@splitsoftware/splitio-commons 3.0.0-rc.0 → 3.0.0-rc.2
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/evaluator/index.js +11 -11
- package/cjs/logger/constants.js +4 -4
- package/cjs/logger/messages/warn.js +0 -1
- package/cjs/readiness/constants.js +4 -4
- package/cjs/readiness/readinessManager.js +22 -22
- package/cjs/readiness/sdkReadinessManager.js +0 -16
- package/cjs/sdkClient/sdkLifecycle.js +2 -1
- package/cjs/sdkClient/trackMethod.js +2 -2
- package/cjs/sdkFactory/index.js +5 -5
- package/cjs/sdkManager/index.js +2 -2
- package/cjs/services/authProvider.js +67 -0
- package/cjs/services/secureSplitHttpClient.js +39 -0
- package/cjs/services/splitApi.js +15 -6
- package/cjs/storages/AbstractDefinitionsCacheAsync.js +51 -0
- package/cjs/storages/AbstractDefinitionsCacheSync.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/DefinitionsCacheInLocal.js +194 -0
- package/cjs/storages/inLocalStorage/RBSegmentsCacheInLocal.js +9 -9
- package/cjs/storages/inLocalStorage/index.js +6 -6
- package/cjs/storages/inLocalStorage/validateCache.js +3 -3
- package/cjs/storages/inMemory/DefinitionsCacheInMemory.js +121 -0
- package/cjs/storages/inMemory/InMemoryStorage.js +4 -4
- package/cjs/storages/inMemory/InMemoryStorageCS.js +5 -5
- package/cjs/storages/inMemory/RBSegmentsCacheInMemory.js +4 -4
- package/cjs/storages/inMemory/SegmentsCacheInMemory.js +1 -1
- package/cjs/storages/inMemory/TelemetryCacheInMemory.js +3 -3
- package/cjs/storages/inRedis/{SplitsCacheInRedis.js → DefinitionsCacheInRedis.js} +77 -77
- package/cjs/storages/inRedis/index.js +2 -2
- package/cjs/storages/pluggable/DefinitionsCachePluggable.js +212 -0
- package/cjs/storages/pluggable/index.js +2 -2
- package/cjs/storages/setRolloutPlan.js +5 -5
- package/cjs/sync/offline/syncTasks/fromObjectSyncTask.js +5 -5
- package/cjs/sync/polling/fetchers/splitChangesFetcher.js +1 -1
- package/cjs/sync/polling/pollingManagerCS.js +7 -7
- package/cjs/sync/polling/pollingManagerSS.js +3 -3
- package/cjs/sync/polling/syncTasks/definitionsSyncTask.js +1 -1
- package/cjs/sync/polling/syncTasks/segmentsSyncTask.js +2 -3
- package/cjs/sync/polling/updaters/definitionChangesUpdater.js +8 -8
- package/cjs/sync/polling/updaters/mySegmentsUpdater.js +2 -2
- package/cjs/sync/streaming/SSEHandler/NotificationKeeper.js +1 -1
- package/cjs/sync/streaming/SSEHandler/index.js +1 -1
- package/cjs/sync/streaming/UpdateWorkers/{SplitsUpdateWorker.js → DefinitionsUpdateWorker.js} +15 -15
- package/cjs/sync/streaming/constants.js +3 -3
- package/cjs/sync/streaming/pushManager.js +13 -13
- package/cjs/sync/submitters/telemetrySubmitter.js +1 -2
- package/cjs/sync/syncManagerOnline.js +10 -8
- package/cjs/utils/Backoff.js +20 -0
- package/cjs/utils/lang/sets.js +4 -4
- package/cjs/utils/settingsValidation/index.js +2 -0
- package/cjs/utils/settingsValidation/url.js +6 -2
- package/esm/evaluator/index.js +11 -11
- package/esm/logger/constants.js +1 -1
- package/esm/logger/messages/warn.js +0 -1
- package/esm/readiness/constants.js +3 -3
- package/esm/readiness/readinessManager.js +23 -23
- package/esm/readiness/sdkReadinessManager.js +1 -17
- package/esm/sdkClient/sdkLifecycle.js +2 -1
- package/esm/sdkClient/trackMethod.js +2 -2
- package/esm/sdkFactory/index.js +6 -6
- package/esm/sdkManager/index.js +2 -2
- package/esm/services/authProvider.js +63 -0
- package/esm/services/secureSplitHttpClient.js +35 -0
- package/esm/services/splitApi.js +15 -6
- package/esm/storages/AbstractDefinitionsCacheAsync.js +48 -0
- package/esm/storages/AbstractDefinitionsCacheSync.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/DefinitionsCacheInLocal.js +191 -0
- package/esm/storages/inLocalStorage/RBSegmentsCacheInLocal.js +6 -6
- package/esm/storages/inLocalStorage/index.js +6 -6
- package/esm/storages/inLocalStorage/validateCache.js +3 -3
- package/esm/storages/inMemory/DefinitionsCacheInMemory.js +118 -0
- package/esm/storages/inMemory/InMemoryStorage.js +4 -4
- package/esm/storages/inMemory/InMemoryStorageCS.js +5 -5
- package/esm/storages/inMemory/RBSegmentsCacheInMemory.js +1 -1
- package/esm/storages/inMemory/SegmentsCacheInMemory.js +1 -1
- package/esm/storages/inMemory/TelemetryCacheInMemory.js +3 -3
- package/esm/storages/inRedis/{SplitsCacheInRedis.js → DefinitionsCacheInRedis.js} +76 -76
- package/esm/storages/inRedis/index.js +2 -2
- package/esm/storages/pluggable/DefinitionsCachePluggable.js +209 -0
- package/esm/storages/pluggable/index.js +2 -2
- package/esm/storages/setRolloutPlan.js +5 -5
- package/esm/sync/offline/syncTasks/fromObjectSyncTask.js +6 -6
- package/esm/sync/polling/fetchers/splitChangesFetcher.js +1 -1
- package/esm/sync/polling/pollingManagerCS.js +4 -4
- package/esm/sync/polling/pollingManagerSS.js +3 -3
- package/esm/sync/polling/syncTasks/definitionsSyncTask.js +1 -1
- package/esm/sync/polling/syncTasks/segmentsSyncTask.js +2 -3
- package/esm/sync/polling/updaters/definitionChangesUpdater.js +9 -9
- package/esm/sync/polling/updaters/mySegmentsUpdater.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/{SplitsUpdateWorker.js → DefinitionsUpdateWorker.js} +14 -14
- package/esm/sync/streaming/constants.js +2 -2
- package/esm/sync/streaming/pushManager.js +14 -14
- package/esm/sync/submitters/telemetrySubmitter.js +1 -2
- package/esm/sync/syncManagerOnline.js +9 -7
- package/esm/utils/Backoff.js +20 -0
- package/esm/utils/lang/sets.js +4 -4
- package/esm/utils/settingsValidation/index.js +2 -0
- package/esm/utils/settingsValidation/url.js +6 -2
- package/package.json +1 -1
- package/src/dtos/types.ts +2 -2
- package/src/evaluator/index.ts +11 -11
- package/src/logger/constants.ts +1 -1
- package/src/logger/messages/warn.ts +0 -1
- package/src/readiness/constants.ts +3 -3
- package/src/readiness/readinessManager.ts +22 -22
- package/src/readiness/sdkReadinessManager.ts +1 -16
- package/src/readiness/types.ts +10 -16
- package/src/sdkClient/sdkLifecycle.ts +2 -1
- package/src/sdkClient/trackMethod.ts +2 -2
- package/src/sdkFactory/index.ts +5 -5
- package/src/sdkManager/index.ts +6 -6
- package/src/services/authProvider.ts +86 -0
- package/src/services/secureSplitHttpClient.ts +44 -0
- package/src/services/splitApi.ts +18 -7
- package/src/services/types.ts +6 -1
- package/src/storages/AbstractDefinitionsCacheAsync.ts +63 -0
- package/src/storages/AbstractDefinitionsCacheSync.ts +95 -0
- package/src/storages/KeyBuilder.ts +6 -6
- package/src/storages/KeyBuilderCS.ts +6 -6
- package/src/storages/KeyBuilderSS.ts +2 -2
- package/src/storages/getRolloutPlan.ts +3 -3
- package/src/storages/inLocalStorage/DefinitionsCacheInLocal.ts +234 -0
- package/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +6 -6
- package/src/storages/inLocalStorage/index.ts +6 -6
- package/src/storages/inLocalStorage/validateCache.ts +4 -4
- package/src/storages/inMemory/DefinitionsCacheInMemory.ts +130 -0
- package/src/storages/inMemory/InMemoryStorage.ts +4 -4
- package/src/storages/inMemory/InMemoryStorageCS.ts +5 -5
- package/src/storages/inMemory/RBSegmentsCacheInMemory.ts +1 -1
- package/src/storages/inMemory/SegmentsCacheInMemory.ts +1 -1
- package/src/storages/inMemory/TelemetryCacheInMemory.ts +3 -3
- package/src/storages/inRedis/{SplitsCacheInRedis.ts → DefinitionsCacheInRedis.ts} +67 -67
- package/src/storages/inRedis/index.ts +2 -2
- package/src/storages/pluggable/DefinitionsCachePluggable.ts +230 -0
- package/src/storages/pluggable/index.ts +2 -2
- package/src/storages/setRolloutPlan.ts +7 -7
- package/src/storages/types.ts +19 -19
- package/src/sync/offline/syncTasks/fromObjectSyncTask.ts +8 -8
- package/src/sync/polling/fetchers/splitChangesFetcher.ts +3 -3
- package/src/sync/polling/pollingManagerCS.ts +4 -4
- package/src/sync/polling/pollingManagerSS.ts +5 -4
- package/src/sync/polling/syncTasks/definitionsSyncTask.ts +1 -1
- package/src/sync/polling/syncTasks/segmentsSyncTask.ts +3 -4
- package/src/sync/polling/types.ts +1 -1
- package/src/sync/polling/updaters/definitionChangesUpdater.ts +11 -11
- package/src/sync/polling/updaters/mySegmentsUpdater.ts +1 -1
- package/src/sync/streaming/AuthClient/index.ts +2 -2
- package/src/sync/streaming/AuthClient/types.ts +14 -11
- package/src/sync/streaming/SSEClient/index.ts +2 -2
- package/src/sync/streaming/SSEClient/types.ts +2 -2
- package/src/sync/streaming/SSEHandler/NotificationKeeper.ts +2 -2
- package/src/sync/streaming/SSEHandler/index.ts +2 -2
- package/src/sync/streaming/UpdateWorkers/{SplitsUpdateWorker.ts → DefinitionsUpdateWorker.ts} +16 -16
- package/src/sync/streaming/constants.ts +2 -2
- package/src/sync/streaming/pushManager.ts +16 -16
- package/src/sync/streaming/types.ts +2 -2
- package/src/sync/submitters/telemetrySubmitter.ts +1 -2
- package/src/sync/syncManagerOnline.ts +10 -8
- package/src/utils/Backoff.ts +21 -0
- package/src/utils/inputValidation/trafficTypeExistence.ts +2 -2
- package/src/utils/jwt/types.ts +1 -1
- package/src/utils/lang/sets.ts +4 -2
- package/src/utils/settingsValidation/index.ts +2 -0
- package/src/utils/settingsValidation/url.ts +6 -2
- package/types/splitio.d.ts +27 -8
- package/cjs/storages/AbstractSplitsCacheAsync.js +0 -51
- package/cjs/storages/AbstractSplitsCacheSync.js +0 -76
- package/cjs/storages/inLocalStorage/SplitsCacheInLocal.js +0 -194
- package/cjs/storages/inMemory/SplitsCacheInMemory.js +0 -121
- package/cjs/storages/pluggable/SplitsCachePluggable.js +0 -212
- package/esm/storages/AbstractSplitsCacheAsync.js +0 -48
- package/esm/storages/AbstractSplitsCacheSync.js +0 -71
- package/esm/storages/inLocalStorage/SplitsCacheInLocal.js +0 -191
- package/esm/storages/inMemory/SplitsCacheInMemory.js +0 -118
- package/esm/storages/pluggable/SplitsCachePluggable.js +0 -209
- package/src/storages/AbstractSplitsCacheAsync.ts +0 -63
- package/src/storages/AbstractSplitsCacheSync.ts +0 -95
- package/src/storages/inLocalStorage/SplitsCacheInLocal.ts +0 -234
- package/src/storages/inMemory/SplitsCacheInMemory.ts +0 -130
- package/src/storages/pluggable/SplitsCachePluggable.ts +0 -230
|
@@ -15,7 +15,6 @@ export const codesWarn: [number, string][] = codesError.concat([
|
|
|
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
17
|
[c.CLIENT_NOT_READY_FROM_CACHE, '%s: the SDK is not ready to evaluate. Results may be incorrect. 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
18
|
// input validation
|
|
20
19
|
[c.WARN_SETTING_NULL, '%s: Property "%s" is of invalid type. Setting value to null.'],
|
|
21
20
|
[c.WARN_TRIMMING_PROPERTIES, '%s: more than 300 properties were provided. Some of them will be trimmed when processed.'],
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
//
|
|
2
|
-
export const
|
|
3
|
-
export const
|
|
1
|
+
// Definitions events:
|
|
2
|
+
export const SDK_DEFINITIONS_ARRIVED = 'state::splits-arrived';
|
|
3
|
+
export const SDK_DEFINITIONS_CACHE_LOADED = 'state::splits-cache-loaded';
|
|
4
4
|
|
|
5
5
|
// Segments events:
|
|
6
6
|
export const SDK_SEGMENTS_ARRIVED = 'state::segments-arrived';
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
2
2
|
import { ISettings } from '../types';
|
|
3
3
|
import SplitIO, { SdkReadyMetadata } from '../../types/splitio';
|
|
4
|
-
import {
|
|
5
|
-
import { IReadinessEventEmitter, IReadinessManager, ISegmentsEventEmitter,
|
|
4
|
+
import { SDK_DEFINITIONS_ARRIVED, SDK_DEFINITIONS_CACHE_LOADED, SDK_SEGMENTS_ARRIVED, SDK_READY_TIMED_OUT, SDK_READY_FROM_CACHE, SDK_UPDATE, SDK_READY } from './constants';
|
|
5
|
+
import { IReadinessEventEmitter, IReadinessManager, ISegmentsEventEmitter, IDefinitionsEventEmitter } from './types';
|
|
6
6
|
|
|
7
|
-
function
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
function definitionsEventEmitterFactory(EventEmitter: new () => SplitIO.IEventEmitter): IDefinitionsEventEmitter {
|
|
8
|
+
const definitionsEventEmitter = objectAssign(new EventEmitter(), {
|
|
9
|
+
definitionsArrived: false,
|
|
10
|
+
definitionsCacheLoaded: false,
|
|
11
11
|
hasInit: false,
|
|
12
12
|
initCallbacks: []
|
|
13
13
|
});
|
|
@@ -15,10 +15,10 @@ function splitsEventEmitterFactory(EventEmitter: new () => SplitIO.IEventEmitter
|
|
|
15
15
|
// `isSplitKill` condition avoids an edge-case of wrongly emitting SDK_READY if:
|
|
16
16
|
// - `/memberships` fetch and SPLIT_KILL occurs before `/splitChanges` fetch, and
|
|
17
17
|
// - storage has cached splits (for which case `splitsStorage.killLocally` can return true)
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
definitionsEventEmitter.on(SDK_DEFINITIONS_ARRIVED, (metadata: SplitIO.SdkUpdateMetadata, isSplitKill: boolean) => { if (!isSplitKill) definitionsEventEmitter.definitionsArrived = true; });
|
|
19
|
+
definitionsEventEmitter.once(SDK_DEFINITIONS_CACHE_LOADED, () => { definitionsEventEmitter.definitionsCacheLoaded = true; });
|
|
20
20
|
|
|
21
|
-
return
|
|
21
|
+
return definitionsEventEmitter;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
function segmentsEventEmitterFactory(EventEmitter: new () => SplitIO.IEventEmitter): ISegmentsEventEmitter {
|
|
@@ -37,7 +37,7 @@ function segmentsEventEmitterFactory(EventEmitter: new () => SplitIO.IEventEmitt
|
|
|
37
37
|
export function readinessManagerFactory(
|
|
38
38
|
EventEmitter: new () => SplitIO.IEventEmitter,
|
|
39
39
|
settings: ISettings,
|
|
40
|
-
|
|
40
|
+
definitions: IDefinitionsEventEmitter = definitionsEventEmitterFactory(EventEmitter),
|
|
41
41
|
isShared?: boolean
|
|
42
42
|
): IReadinessManager {
|
|
43
43
|
|
|
@@ -59,8 +59,8 @@ export function readinessManagerFactory(
|
|
|
59
59
|
|
|
60
60
|
// emit SDK_READY_FROM_CACHE
|
|
61
61
|
let isReadyFromCache = false;
|
|
62
|
-
if (
|
|
63
|
-
else
|
|
62
|
+
if (definitions.definitionsCacheLoaded) isReadyFromCache = true; // ready from cache, but doesn't emit SDK_READY_FROM_CACHE
|
|
63
|
+
else definitions.once(SDK_DEFINITIONS_CACHE_LOADED, checkIsReadyFromCache);
|
|
64
64
|
|
|
65
65
|
// emit SDK_READY_TIMED_OUT
|
|
66
66
|
let hasTimedout = false;
|
|
@@ -75,7 +75,7 @@ export function readinessManagerFactory(
|
|
|
75
75
|
|
|
76
76
|
// emit SDK_READY and SDK_UPDATE
|
|
77
77
|
let isReady = false;
|
|
78
|
-
|
|
78
|
+
definitions.on(SDK_DEFINITIONS_ARRIVED, checkIsReadyOrUpdate);
|
|
79
79
|
segments.on(SDK_SEGMENTS_ARRIVED, checkIsReadyOrUpdate);
|
|
80
80
|
|
|
81
81
|
let isDestroyed = false;
|
|
@@ -85,8 +85,8 @@ export function readinessManagerFactory(
|
|
|
85
85
|
if (readyTimeout > 0 && !isReady) readyTimeoutId = setTimeout(timeout, readyTimeout);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
if (
|
|
88
|
+
definitions.initCallbacks.push(__init);
|
|
89
|
+
if (definitions.hasInit) __init();
|
|
90
90
|
|
|
91
91
|
function checkIsReadyFromCache(cacheMetadata: SdkReadyMetadata) {
|
|
92
92
|
metadataReady = cacheMetadata;
|
|
@@ -114,7 +114,7 @@ export function readinessManagerFactory(
|
|
|
114
114
|
setTimeout(() => { throw e; }, 0);
|
|
115
115
|
}
|
|
116
116
|
} else {
|
|
117
|
-
if (
|
|
117
|
+
if (definitions.definitionsArrived && segments.segmentsArrived) {
|
|
118
118
|
clearTimeout(readyTimeoutId);
|
|
119
119
|
isReady = true;
|
|
120
120
|
try {
|
|
@@ -137,12 +137,12 @@ export function readinessManagerFactory(
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
return {
|
|
140
|
-
|
|
140
|
+
definitions,
|
|
141
141
|
segments,
|
|
142
142
|
gate,
|
|
143
143
|
|
|
144
144
|
shared() {
|
|
145
|
-
return readinessManagerFactory(EventEmitter, settings,
|
|
145
|
+
return readinessManagerFactory(EventEmitter, settings, definitions, true);
|
|
146
146
|
},
|
|
147
147
|
|
|
148
148
|
// @TODO review/remove next methods when non-recoverable errors are reworked
|
|
@@ -153,9 +153,9 @@ export function readinessManagerFactory(
|
|
|
153
153
|
setDestroyed() { isDestroyed = true; },
|
|
154
154
|
|
|
155
155
|
init() {
|
|
156
|
-
if (
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
if (definitions.hasInit) return;
|
|
157
|
+
definitions.hasInit = true;
|
|
158
|
+
definitions.initCallbacks.forEach(cb => cb());
|
|
159
159
|
},
|
|
160
160
|
|
|
161
161
|
destroy() {
|
|
@@ -163,7 +163,7 @@ export function readinessManagerFactory(
|
|
|
163
163
|
syncLastUpdate();
|
|
164
164
|
clearTimeout(readyTimeoutId);
|
|
165
165
|
|
|
166
|
-
if (!isShared)
|
|
166
|
+
if (!isShared) definitions.hasInit = false;
|
|
167
167
|
},
|
|
168
168
|
|
|
169
169
|
isReady() { return isReady; },
|
|
@@ -5,10 +5,9 @@ import { ISdkReadinessManager } from './types';
|
|
|
5
5
|
import { ISettings } from '../types';
|
|
6
6
|
import SplitIO from '../../types/splitio';
|
|
7
7
|
import { SDK_READY, SDK_READY_TIMED_OUT, SDK_READY_FROM_CACHE, SDK_UPDATE } from './constants';
|
|
8
|
-
import { ERROR_CLIENT_LISTENER, CLIENT_READY_FROM_CACHE, CLIENT_READY
|
|
8
|
+
import { ERROR_CLIENT_LISTENER, CLIENT_READY_FROM_CACHE, CLIENT_READY } from '../logger/constants';
|
|
9
9
|
|
|
10
10
|
const NEW_LISTENER_EVENT = 'newListener';
|
|
11
|
-
const REMOVE_LISTENER_EVENT = 'removeListener';
|
|
12
11
|
const TIMEOUT_ERROR = new Error(SDK_READY_TIMED_OUT);
|
|
13
12
|
|
|
14
13
|
/**
|
|
@@ -25,19 +24,10 @@ export function sdkReadinessManagerFactory(
|
|
|
25
24
|
|
|
26
25
|
const log = settings.log;
|
|
27
26
|
|
|
28
|
-
/** Ready callback warning */
|
|
29
|
-
let internalReadyCbCount = 0;
|
|
30
|
-
let readyCbCount = 0;
|
|
31
|
-
readinessManager.gate.on(REMOVE_LISTENER_EVENT, (event: any) => {
|
|
32
|
-
if (event === SDK_READY) readyCbCount--;
|
|
33
|
-
});
|
|
34
|
-
|
|
35
27
|
readinessManager.gate.on(NEW_LISTENER_EVENT, (event: any) => {
|
|
36
28
|
if (event === SDK_READY || event === SDK_READY_TIMED_OUT) {
|
|
37
29
|
if (readinessManager.isReady()) {
|
|
38
30
|
log.error(ERROR_CLIENT_LISTENER, [event === SDK_READY ? 'SDK_READY' : 'SDK_READY_TIMED_OUT']);
|
|
39
|
-
} else if (event === SDK_READY) {
|
|
40
|
-
readyCbCount++;
|
|
41
31
|
}
|
|
42
32
|
} else if (event === SDK_READY_FROM_CACHE && readinessManager.isReadyFromCache()) {
|
|
43
33
|
log.error(ERROR_CLIENT_LISTENER, ['SDK_READY_FROM_CACHE']);
|
|
@@ -61,7 +51,6 @@ export function sdkReadinessManagerFactory(
|
|
|
61
51
|
readinessManager.gate.once(SDK_READY, () => {
|
|
62
52
|
log.info(CLIENT_READY);
|
|
63
53
|
|
|
64
|
-
if (readyCbCount === internalReadyCbCount && !promise.hasOnFulfilled()) log.warn(CLIENT_NO_LISTENER);
|
|
65
54
|
resolve();
|
|
66
55
|
});
|
|
67
56
|
readinessManager.gate.once(SDK_READY_TIMED_OUT, (message: string) => {
|
|
@@ -91,10 +80,6 @@ export function sdkReadinessManagerFactory(
|
|
|
91
80
|
return sdkReadinessManagerFactory(EventEmitter, settings, readinessManager.shared());
|
|
92
81
|
},
|
|
93
82
|
|
|
94
|
-
incInternalReadyCbCount() {
|
|
95
|
-
internalReadyCbCount++;
|
|
96
|
-
},
|
|
97
|
-
|
|
98
83
|
sdkStatus: objectAssign(
|
|
99
84
|
// Expose Event Emitter functionality
|
|
100
85
|
Object.create(readinessManager.gate),
|
package/src/readiness/types.ts
CHANGED
|
@@ -16,17 +16,17 @@ export interface IReadinessEventEmitter extends SplitIO.IEventEmitter {
|
|
|
16
16
|
|
|
17
17
|
/** Splits data emitter */
|
|
18
18
|
|
|
19
|
-
type
|
|
20
|
-
type
|
|
21
|
-
type
|
|
19
|
+
type SDK_DEFINITIONS_ARRIVED = 'state::splits-arrived'
|
|
20
|
+
type SDK_DEFINITIONS_CACHE_LOADED = 'state::splits-cache-loaded'
|
|
21
|
+
type IDefinitionsEvent = SDK_DEFINITIONS_ARRIVED | SDK_DEFINITIONS_CACHE_LOADED
|
|
22
22
|
|
|
23
|
-
export interface
|
|
24
|
-
emit(event:
|
|
25
|
-
on(event:
|
|
23
|
+
export interface IDefinitionsEventEmitter extends SplitIO.IEventEmitter {
|
|
24
|
+
emit(event: IDefinitionsEvent, ...args: any[]): boolean
|
|
25
|
+
on(event: IDefinitionsEvent, listener: (...args: any[]) => void): this;
|
|
26
26
|
on(event: SDK_UPDATE, listener: (metadata: SplitIO.SdkUpdateMetadata) => void): this;
|
|
27
|
-
once(event:
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
once(event: IDefinitionsEvent, listener: (...args: any[]) => void): this;
|
|
28
|
+
definitionsArrived: boolean
|
|
29
|
+
definitionsCacheLoaded: boolean
|
|
30
30
|
hasInit: boolean,
|
|
31
31
|
initCallbacks: (() => void)[]
|
|
32
32
|
}
|
|
@@ -48,7 +48,7 @@ export interface ISegmentsEventEmitter extends SplitIO.IEventEmitter {
|
|
|
48
48
|
|
|
49
49
|
export interface IReadinessManager {
|
|
50
50
|
/** Event emitters */
|
|
51
|
-
|
|
51
|
+
definitions: IDefinitionsEventEmitter,
|
|
52
52
|
segments: ISegmentsEventEmitter,
|
|
53
53
|
gate: IReadinessEventEmitter,
|
|
54
54
|
|
|
@@ -77,12 +77,6 @@ export interface ISdkReadinessManager {
|
|
|
77
77
|
readinessManager: IReadinessManager
|
|
78
78
|
sdkStatus: SplitIO.IStatusInterface
|
|
79
79
|
|
|
80
|
-
/**
|
|
81
|
-
* Increment internalReadyCbCount, an offset value of SDK_READY listeners that are added/removed internally
|
|
82
|
-
* by the SDK. It is required to properly log the warning 'No listeners for SDK Readiness detected'
|
|
83
|
-
*/
|
|
84
|
-
incInternalReadyCbCount(): void
|
|
85
|
-
|
|
86
80
|
/** for client-side */
|
|
87
81
|
shared(): ISdkReadinessManager
|
|
88
82
|
}
|
|
@@ -7,7 +7,7 @@ const COOLDOWN_TIME_IN_MILLIS = 1000;
|
|
|
7
7
|
* Creates an Sdk client, i.e., a base client with status, init, flush and destroy interface
|
|
8
8
|
*/
|
|
9
9
|
export function sdkLifecycleFactory(params: ISdkFactoryContext, isSharedClient?: boolean): { init(): void; flush(): Promise<void>; destroy(): Promise<void> } {
|
|
10
|
-
const { sdkReadinessManager, syncManager, storage, settings, telemetryTracker, impressionsTracker, platform } = params;
|
|
10
|
+
const { sdkReadinessManager, syncManager, storage, settings, telemetryTracker, impressionsTracker, platform, splitApi } = params;
|
|
11
11
|
|
|
12
12
|
let hasInit = false;
|
|
13
13
|
let lastActionTime = 0;
|
|
@@ -68,6 +68,7 @@ export function sdkLifecycleFactory(params: ISdkFactoryContext, isSharedClient?:
|
|
|
68
68
|
|
|
69
69
|
// Stop background jobs
|
|
70
70
|
syncManager && syncManager.stop();
|
|
71
|
+
splitApi && splitApi.stop();
|
|
71
72
|
|
|
72
73
|
return __flush().then(() => {
|
|
73
74
|
// Cleanup storage
|
|
@@ -12,7 +12,7 @@ import { ISdkFactoryContext } from '../sdkFactory/types';
|
|
|
12
12
|
* Reusable by FF SDK client, Configs SDK, and thin-client SDK.
|
|
13
13
|
*/
|
|
14
14
|
export function trackMethodFactory(params: Pick<ISdkFactoryContext, 'settings' | 'eventTracker' | 'telemetryTracker' | 'storage' | 'sdkReadinessManager'>, warnTTExistence = true) {
|
|
15
|
-
const { settings, storage: {
|
|
15
|
+
const { settings, storage: { definitions }, telemetryTracker, eventTracker, sdkReadinessManager: { readinessManager } } = params;
|
|
16
16
|
const { log, mode } = settings;
|
|
17
17
|
const isAsync = isConsumerMode(mode);
|
|
18
18
|
|
|
@@ -42,7 +42,7 @@ export function trackMethodFactory(params: Pick<ISdkFactoryContext, 'settings' |
|
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
// This may be async but we only warn, we don't actually care if it is valid or not in terms of queueing the event.
|
|
45
|
-
if (warnTTExistence) validateTrafficTypeExistence(log, readinessManager,
|
|
45
|
+
if (warnTTExistence) validateTrafficTypeExistence(log, readinessManager, definitions, mode, trafficTypeName, TRACK_FN_LABEL);
|
|
46
46
|
|
|
47
47
|
const result = eventTracker.track(eventData, size);
|
|
48
48
|
|
package/src/sdkFactory/index.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { telemetryTrackerFactory } from '../trackers/telemetryTracker';
|
|
|
6
6
|
import SplitIO from '../../types/splitio';
|
|
7
7
|
import { createLoggerAPI } from '../logger/sdkLogger';
|
|
8
8
|
import { NEW_FACTORY, RETRIEVE_MANAGER } from '../logger/constants';
|
|
9
|
-
import {
|
|
9
|
+
import { SDK_DEFINITIONS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_DEFINITIONS_CACHE_LOADED } from '../readiness/constants';
|
|
10
10
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
11
11
|
import { setRolloutPlan } from '../storages/setRolloutPlan';
|
|
12
12
|
import { IStorageSync } from '../storages/types';
|
|
@@ -36,11 +36,11 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
|
|
|
36
36
|
readiness.timeout();
|
|
37
37
|
return;
|
|
38
38
|
}
|
|
39
|
-
readiness.
|
|
39
|
+
readiness.definitions.emit(SDK_DEFINITIONS_ARRIVED);
|
|
40
40
|
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
|
|
41
41
|
},
|
|
42
42
|
onReadyFromCacheCb() {
|
|
43
|
-
readiness.
|
|
43
|
+
readiness.definitions.emit(SDK_DEFINITIONS_CACHE_LOADED);
|
|
44
44
|
}
|
|
45
45
|
});
|
|
46
46
|
|
|
@@ -48,7 +48,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
|
|
|
48
48
|
|
|
49
49
|
if (initialRolloutPlan) {
|
|
50
50
|
setRolloutPlan(log, initialRolloutPlan, storage as IStorageSync, key && getMatching(key));
|
|
51
|
-
if ((storage as IStorageSync).
|
|
51
|
+
if ((storage as IStorageSync).definitions.getChangeNumber() > -1) readiness.definitions.emit(SDK_DEFINITIONS_CACHE_LOADED, { initialCacheLoad: false /* Not an initial load, cache exists */ });
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
const clients: Record<string, SplitIO.IBasicClient & { init: () => void }> = {};
|
|
@@ -66,7 +66,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
|
|
|
66
66
|
|
|
67
67
|
// SDK client and manager
|
|
68
68
|
const clientMethod = sdkClientMethodFactory(ctx);
|
|
69
|
-
const managerInstance = sdkManagerFactory(settings, storage.
|
|
69
|
+
const managerInstance = sdkManagerFactory(settings, storage.definitions, sdkReadinessManager);
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
function init() {
|
package/src/sdkManager/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { objectAssign } from '../utils/lang/objectAssign';
|
|
|
2
2
|
import { thenable } from '../utils/promise/thenable';
|
|
3
3
|
import { find } from '../utils/lang';
|
|
4
4
|
import { validateDefinition, validateDefinitionExistence, validateIfOperational } from '../utils/inputValidation';
|
|
5
|
-
import {
|
|
5
|
+
import { IDefinitionsCacheAsync, IDefinitionsCacheSync } from '../storages/types';
|
|
6
6
|
import { ISdkReadinessManager } from '../readiness/types';
|
|
7
7
|
import { IDefinition } from '../dtos/types';
|
|
8
8
|
import { ISettings } from '../types';
|
|
@@ -48,11 +48,11 @@ function objectsToViews(splitObjects: IDefinition[]) {
|
|
|
48
48
|
return views;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
export function sdkManagerFactory<
|
|
51
|
+
export function sdkManagerFactory<TDefinitionsCache extends IDefinitionsCacheSync | IDefinitionsCacheAsync>(
|
|
52
52
|
settings: Pick<ISettings, 'log' | 'mode'>,
|
|
53
|
-
splits:
|
|
53
|
+
splits: TDefinitionsCache,
|
|
54
54
|
{ readinessManager, sdkStatus }: ISdkReadinessManager,
|
|
55
|
-
):
|
|
55
|
+
): TDefinitionsCache extends IDefinitionsCacheAsync ? SplitIO.IAsyncManager : SplitIO.IManager {
|
|
56
56
|
|
|
57
57
|
const { log, mode } = settings;
|
|
58
58
|
const isAsync = isConsumerMode(mode);
|
|
@@ -70,7 +70,7 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
|
|
|
70
70
|
return isAsync ? Promise.resolve(null) : null;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
const split = splits.
|
|
73
|
+
const split = splits.get(splitName);
|
|
74
74
|
|
|
75
75
|
if (thenable(split)) {
|
|
76
76
|
return split.catch(() => null).then(result => { // handle possible rejections when using pluggable storage
|
|
@@ -103,7 +103,7 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
|
|
|
103
103
|
if (!validateIfOperational(log, readinessManager, NAMES_FN_LABEL)) {
|
|
104
104
|
return isAsync ? Promise.resolve([]) : [];
|
|
105
105
|
}
|
|
106
|
-
const splitNames = splits.
|
|
106
|
+
const splitNames = splits.getNames();
|
|
107
107
|
|
|
108
108
|
return thenable(splitNames) ?
|
|
109
109
|
splitNames.catch(() => []) : // handle possible rejections when using pluggable storage
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { ISplitHttpClient, NetworkError } from './types';
|
|
2
|
+
import { IJwtCredentialV3 } from '../sync/streaming/AuthClient/types';
|
|
3
|
+
import { authenticateFactory } from '../sync/streaming/AuthClient';
|
|
4
|
+
import { Backoff } from '../utils/Backoff';
|
|
5
|
+
import { LOG_PREFIX_SYNC_AUTH } from '../logger/constants';
|
|
6
|
+
import { ISettings } from '../types';
|
|
7
|
+
import { TOKEN } from '../utils/constants';
|
|
8
|
+
import { ITelemetryTracker } from '../trackers/types';
|
|
9
|
+
|
|
10
|
+
const SKEW_SECONDS = 30;
|
|
11
|
+
|
|
12
|
+
function isExpired(credential: IJwtCredentialV3): boolean {
|
|
13
|
+
return Date.now() / 1000 + SKEW_SECONDS >= credential.decodedToken.exp;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface IAuthProvider {
|
|
17
|
+
credential(): Promise<IJwtCredentialV3>;
|
|
18
|
+
invalidate(): void;
|
|
19
|
+
stop(): void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Factory of AuthProvider, which provides JWT credentials for authenticated HTTP requests.
|
|
24
|
+
* Credentials are fetched lazily on demand, cached in memory, and retried with backoff on failure.
|
|
25
|
+
*/
|
|
26
|
+
export function authProviderFactory(settings: ISettings, splitHttpClient: ISplitHttpClient, telemetryTracker: ITelemetryTracker): IAuthProvider {
|
|
27
|
+
|
|
28
|
+
const { urls, log } = settings;
|
|
29
|
+
|
|
30
|
+
function fetchAuth() {
|
|
31
|
+
let url = `${urls.auth}/v3/auth?capabilities=config`;
|
|
32
|
+
return splitHttpClient(url, undefined, telemetryTracker.trackHttp(TOKEN));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const authenticate = authenticateFactory(fetchAuth);
|
|
36
|
+
const backoff = new Backoff(fetchCredential);
|
|
37
|
+
|
|
38
|
+
let cachedCredential: IJwtCredentialV3 | undefined;
|
|
39
|
+
let inFlightPromise: Promise<IJwtCredentialV3> | undefined;
|
|
40
|
+
let stopped = false;
|
|
41
|
+
|
|
42
|
+
function fetchCredential(): Promise<IJwtCredentialV3> {
|
|
43
|
+
return authenticate().then((credential: IJwtCredentialV3) => {
|
|
44
|
+
log.info(LOG_PREFIX_SYNC_AUTH + 'credential fetched successfully');
|
|
45
|
+
cachedCredential = credential;
|
|
46
|
+
inFlightPromise = undefined;
|
|
47
|
+
backoff.reset();
|
|
48
|
+
return credential;
|
|
49
|
+
}).catch((error: NetworkError) => {
|
|
50
|
+
// Avoid rejected promises and unnecessary retries after stop()
|
|
51
|
+
if (stopped) return cachedCredential!;
|
|
52
|
+
|
|
53
|
+
if (error.statusCode && error.statusCode >= 400 && error.statusCode < 500) {
|
|
54
|
+
log.error(LOG_PREFIX_SYNC_AUTH + 'non-retryable error fetching credential (status ' + error.statusCode + '): ' + error.message);
|
|
55
|
+
inFlightPromise = undefined;
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
log.warn(LOG_PREFIX_SYNC_AUTH + 'credential fetch failed (attempt ' + (backoff.attempts + 1) + '). Error: ' + error.message);
|
|
60
|
+
return backoff.scheduleCallAsync();
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
credential(): Promise<IJwtCredentialV3> {
|
|
66
|
+
if (cachedCredential && !isExpired(cachedCredential)) {
|
|
67
|
+
return Promise.resolve(cachedCredential);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (cachedCredential) log.debug(LOG_PREFIX_SYNC_AUTH + 'cached credential expired');
|
|
71
|
+
|
|
72
|
+
return inFlightPromise || (inFlightPromise = fetchCredential());
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
invalidate() {
|
|
76
|
+
cachedCredential = undefined;
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
stop() {
|
|
80
|
+
stopped = true;
|
|
81
|
+
cachedCredential = undefined;
|
|
82
|
+
inFlightPromise = undefined;
|
|
83
|
+
backoff.reset();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { IRequestOptions, IResponse, ISecureSplitHttpClient, NetworkError } from './types';
|
|
2
|
+
import { ISettings } from '../types';
|
|
3
|
+
import { IPlatform } from '../sdkFactory/types';
|
|
4
|
+
import { splitHttpClientFactory } from './splitHttpClient';
|
|
5
|
+
import { authProviderFactory } from './authProvider';
|
|
6
|
+
import { ITelemetryTracker } from '../trackers/types';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Factory of Secure HTTP client, which authenticates requests using a JWT token.
|
|
10
|
+
* On 401 responses, invalidates the cached credential and retries once with a fresh token.
|
|
11
|
+
*
|
|
12
|
+
* @param settings - SDK settings
|
|
13
|
+
* @param platform - object containing environment-specific dependencies
|
|
14
|
+
* @param fetchAuth - function to fetch auth credentials from the /v2/auth endpoint
|
|
15
|
+
*/
|
|
16
|
+
export function secureSplitHttpClientFactory(settings: ISettings, platform: Pick<IPlatform, 'getOptions' | 'getFetch'>, telemetryTracker: ITelemetryTracker): ISecureSplitHttpClient {
|
|
17
|
+
|
|
18
|
+
const splitHttpClient = splitHttpClientFactory(settings, platform);
|
|
19
|
+
const authProvider = authProviderFactory(settings, splitHttpClient, telemetryTracker);
|
|
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);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const httpClient = function (url: string, options?: IRequestOptions, latencyTracker?: (error?: NetworkError) => void, logErrorsAsInfo?: boolean): Promise<IResponse> {
|
|
26
|
+
return authProvider.credential().then(credential => {
|
|
27
|
+
return makeRequest(url, options, latencyTracker, logErrorsAsInfo, credential.token)
|
|
28
|
+
.catch((error: NetworkError) => {
|
|
29
|
+
if (error.statusCode === 401) {
|
|
30
|
+
// retry once for 401, in case the token has just expired
|
|
31
|
+
authProvider.invalidate();
|
|
32
|
+
return authProvider.credential().then(newCredential => {
|
|
33
|
+
return makeRequest(url, options, latencyTracker, logErrorsAsInfo, newCredential.token);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
throw error;
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
httpClient.stop = () => authProvider.stop();
|
|
42
|
+
|
|
43
|
+
return httpClient;
|
|
44
|
+
}
|
package/src/services/splitApi.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { IPlatform } from '../sdkFactory/types';
|
|
2
2
|
import { ISettings } from '../types';
|
|
3
3
|
import { splitHttpClientFactory } from './splitHttpClient';
|
|
4
|
-
import { ISplitApi } from './types';
|
|
4
|
+
import { ISecureSplitHttpClient, ISplitApi } from './types';
|
|
5
5
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
6
6
|
import { ITelemetryTracker } from '../trackers/types';
|
|
7
7
|
import { SPLITS, IMPRESSIONS, IMPRESSIONS_COUNT, EVENTS, TELEMETRY, TOKEN, SEGMENT, MEMBERSHIPS } from '../utils/constants';
|
|
@@ -23,13 +23,15 @@ function userKeyToQueryParam(userKey: string) {
|
|
|
23
23
|
export function splitApiFactory(
|
|
24
24
|
settings: ISettings,
|
|
25
25
|
platform: Pick<IPlatform, 'getOptions' | 'getFetch'>,
|
|
26
|
-
telemetryTracker: ITelemetryTracker
|
|
26
|
+
telemetryTracker: ITelemetryTracker,
|
|
27
|
+
secureSplitHttpClientFactory?: (settings: ISettings, platform: Pick<IPlatform, 'getOptions' | 'getFetch'>, telemetryTracker: ITelemetryTracker) => ISecureSplitHttpClient,
|
|
27
28
|
): ISplitApi {
|
|
28
29
|
|
|
29
30
|
const urls = settings.urls;
|
|
30
31
|
const filterQueryString = settings.sync.__splitFiltersValidation && settings.sync.__splitFiltersValidation.queryString;
|
|
31
32
|
const SplitSDKImpressionsMode = settings.sync.impressionsMode;
|
|
32
33
|
const splitHttpClient = splitHttpClientFactory(settings, platform);
|
|
34
|
+
const secureSplitHttpClient = secureSplitHttpClientFactory ? secureSplitHttpClientFactory(settings, platform, telemetryTracker) : undefined;
|
|
33
35
|
|
|
34
36
|
return {
|
|
35
37
|
// @TODO throw errors if health check requests fail, to log them in the Synchronizer
|
|
@@ -61,15 +63,20 @@ export function splitApiFactory(
|
|
|
61
63
|
});
|
|
62
64
|
},
|
|
63
65
|
|
|
66
|
+
fetchSegmentChanges(since: number, segmentName: string, noCache?: boolean, till?: number) {
|
|
67
|
+
const url = `${urls.sdk}/segmentChanges/${segmentName}?since=${since}${till ? '&till=' + till : ''}`;
|
|
68
|
+
return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined, telemetryTracker.trackHttp(SEGMENT));
|
|
69
|
+
},
|
|
70
|
+
|
|
64
71
|
// @TODO support filterQueryString and handle ERROR_TOO_MANY_SETS error
|
|
65
72
|
fetchConfigs(since: number, noCache?: boolean, till?: number) {
|
|
66
|
-
const url = `${urls.
|
|
67
|
-
return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined);
|
|
73
|
+
const url = `${urls.configs}/v1/configs?since=${since}${filterQueryString || ''}${till ? '&till=' + till : ''}`;
|
|
74
|
+
return (secureSplitHttpClient || splitHttpClient)(url, noCache ? noCacheHeaderOptions : undefined);
|
|
68
75
|
},
|
|
69
76
|
|
|
70
|
-
|
|
71
|
-
const url = `${urls.
|
|
72
|
-
return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined
|
|
77
|
+
fetchConfigsSegmentChanges(since: number, segmentName: string, noCache?: boolean, till?: number) {
|
|
78
|
+
const url = `${urls.configs}/v1/segmentChanges/${segmentName}?since=${since}${till ? '&till=' + till : ''}`;
|
|
79
|
+
return (secureSplitHttpClient || splitHttpClient)(url, noCache ? noCacheHeaderOptions : undefined);
|
|
73
80
|
},
|
|
74
81
|
|
|
75
82
|
fetchMemberships(userMatchingKey: string, noCache?: boolean, till?: number) {
|
|
@@ -149,6 +156,10 @@ export function splitApiFactory(
|
|
|
149
156
|
postMetricsUsage(body: string, headers?: Record<string, string>) {
|
|
150
157
|
const url = `${urls.telemetry}/v1/metrics/usage`;
|
|
151
158
|
return splitHttpClient(url, { method: 'POST', body, headers }, telemetryTracker.trackHttp(TELEMETRY), true);
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
stop() {
|
|
162
|
+
if (secureSplitHttpClient) secureSplitHttpClient.stop();
|
|
152
163
|
}
|
|
153
164
|
};
|
|
154
165
|
}
|
package/src/services/types.ts
CHANGED
|
@@ -33,6 +33,8 @@ export type IHealthCheckAPI = () => Promise<boolean>
|
|
|
33
33
|
|
|
34
34
|
export type ISplitHttpClient = (url: string, options?: IRequestOptions, latencyTracker?: (error?: NetworkError) => void, logErrorsAsInfo?: boolean) => Promise<IResponse>
|
|
35
35
|
|
|
36
|
+
export type ISecureSplitHttpClient = ISplitHttpClient & { stop(): void }
|
|
37
|
+
|
|
36
38
|
export type IFetchAuth = (userKeys?: string[]) => Promise<IResponse>
|
|
37
39
|
|
|
38
40
|
export type IFetchDefinitionChanges = (since: number, noCache?: boolean, till?: number, rbSince?: number) => Promise<IResponse>
|
|
@@ -60,8 +62,9 @@ export interface ISplitApi {
|
|
|
60
62
|
getEventsAPIHealthCheck: IHealthCheckAPI
|
|
61
63
|
fetchAuth: IFetchAuth
|
|
62
64
|
fetchSplitChanges: IFetchDefinitionChanges
|
|
63
|
-
fetchConfigs: IFetchDefinitionChanges
|
|
64
65
|
fetchSegmentChanges: IFetchSegmentChanges
|
|
66
|
+
fetchConfigs: IFetchDefinitionChanges
|
|
67
|
+
fetchConfigsSegmentChanges: IFetchSegmentChanges
|
|
65
68
|
fetchMemberships: IFetchMemberships
|
|
66
69
|
postEventsBulk: IPostEventsBulk
|
|
67
70
|
postUniqueKeysBulkCs: IPostUniqueKeysBulkCs
|
|
@@ -70,6 +73,8 @@ export interface ISplitApi {
|
|
|
70
73
|
postTestImpressionsCount: IPostTestImpressionsCount
|
|
71
74
|
postMetricsConfig: IPostMetricsConfig
|
|
72
75
|
postMetricsUsage: IPostMetricsUsage
|
|
76
|
+
// lifecycle
|
|
77
|
+
stop(): void
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
// Minimal version of EventSource API used by the SDK
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { IDefinitionsCacheAsync } from './types';
|
|
2
|
+
import { IDefinition } from '../dtos/types';
|
|
3
|
+
import { objectAssign } from '../utils/lang/objectAssign';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This class provides a skeletal implementation of the IDefinitionsCacheAsync interface
|
|
7
|
+
* to minimize the effort required to implement this interface.
|
|
8
|
+
*/
|
|
9
|
+
export abstract class AbstractDefinitionsCacheAsync implements IDefinitionsCacheAsync {
|
|
10
|
+
|
|
11
|
+
protected abstract add(definition: IDefinition): Promise<boolean>
|
|
12
|
+
protected abstract remove(name: string): Promise<boolean>
|
|
13
|
+
protected abstract setChangeNumber(changeNumber: number): Promise<boolean | void>
|
|
14
|
+
|
|
15
|
+
update(toAdd: IDefinition[], toRemove: string[], changeNumber: number): Promise<boolean> {
|
|
16
|
+
return Promise.all([
|
|
17
|
+
this.setChangeNumber(changeNumber),
|
|
18
|
+
Promise.all(toAdd.map(addedFF => this.add(addedFF))),
|
|
19
|
+
Promise.all(toRemove.map(removedFF => this.remove(removedFF)))
|
|
20
|
+
]).then(([, added, removed]) => {
|
|
21
|
+
return added.some(result => result) || removed.some(result => result);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
abstract get(name: string): Promise<IDefinition | null>
|
|
26
|
+
abstract getMany(names: string[]): Promise<Record<string, IDefinition | null>>
|
|
27
|
+
abstract getChangeNumber(): Promise<number>
|
|
28
|
+
abstract getAll(): Promise<IDefinition[]>
|
|
29
|
+
abstract getNames(): Promise<string[]>
|
|
30
|
+
abstract getNamesBySets(sets: string[]): Promise<Set<string>[]>
|
|
31
|
+
abstract trafficTypeExists(trafficType: string): Promise<boolean>
|
|
32
|
+
abstract clear(): Promise<boolean | void>
|
|
33
|
+
|
|
34
|
+
// @TODO revisit segment-related methods ('usesSegments', 'getRegisteredSegments', 'registerSegments')
|
|
35
|
+
// noop, just keeping the interface. This is used by standalone client-side API only, and so only implemented by InMemory and InLocalStorage.
|
|
36
|
+
usesSegments(): Promise<boolean> {
|
|
37
|
+
return Promise.resolve(true);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Kill `name` definition and set `defaultTreatment` and `changeNumber`.
|
|
42
|
+
* Used for SPLIT_KILL push notifications.
|
|
43
|
+
*
|
|
44
|
+
* @returns a promise that is resolved once the definition kill operation is performed. The fulfillment value is a boolean: `true` if the operation succeeded updating the definition or `false` if no definition is updated,
|
|
45
|
+
* for instance, if the `changeNumber` is old, or if the definition is not found (e.g., `/splitchanges` hasn't been fetched yet), or if the storage fails to apply the update.
|
|
46
|
+
* The promise will never be rejected.
|
|
47
|
+
*/
|
|
48
|
+
killLocally(name: string, defaultTreatment: string, changeNumber: number): Promise<boolean> {
|
|
49
|
+
return this.get(name).then(definition => {
|
|
50
|
+
|
|
51
|
+
if (definition && (!definition.changeNumber || definition.changeNumber < changeNumber)) {
|
|
52
|
+
const newDefinition = objectAssign({}, definition);
|
|
53
|
+
newDefinition.killed = true;
|
|
54
|
+
newDefinition.defaultTreatment = defaultTreatment;
|
|
55
|
+
newDefinition.changeNumber = changeNumber;
|
|
56
|
+
|
|
57
|
+
return this.add(newDefinition);
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
}).catch(() => false);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
}
|