@splitsoftware/splitio-commons 3.1.1-rc.3 → 3.1.1-rc.5
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/cjs/presets/serverSide.js +3 -3
- package/cjs/services/authProvider.js +3 -4
- package/cjs/services/secureSplitHttpClient.js +3 -8
- package/cjs/services/serviceApi.js +17 -15
- package/cjs/services/splitApi.js +10 -9
- 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/esm/presets/serverSide.js +3 -3
- package/esm/services/authProvider.js +4 -5
- package/esm/services/secureSplitHttpClient.js +3 -8
- package/esm/services/serviceApi.js +18 -16
- package/esm/services/splitApi.js +10 -9
- 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/package.json +1 -1
- package/src/dtos/types.ts +1 -1
- package/src/presets/serverSide.ts +3 -3
- package/src/services/authProvider.ts +19 -13
- package/src/services/secureSplitHttpClient.ts +5 -13
- package/src/services/serviceApi.ts +21 -18
- package/src/services/splitApi.ts +10 -9
- package/src/services/types.ts +2 -2
- 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 +1 -1
- package/src/sync/syncManagerOnline.ts +2 -8
|
@@ -4,11 +4,10 @@ import { SSEHandlerFactory } from './SSEHandler';
|
|
|
4
4
|
import { MySegmentsUpdateWorker } from './UpdateWorkers/MySegmentsUpdateWorker';
|
|
5
5
|
import { SegmentsUpdateWorker } from './UpdateWorkers/SegmentsUpdateWorker';
|
|
6
6
|
import { DefinitionsUpdateWorker } from './UpdateWorkers/DefinitionsUpdateWorker';
|
|
7
|
-
import { authenticateFactory, hashUserKey } from './AuthClient';
|
|
8
7
|
import { forOwn } from '../../utils/lang';
|
|
9
8
|
import { SSEClient } from './SSEClient';
|
|
10
9
|
import { checkIfServerSide, getMatching } from '../../utils/key';
|
|
11
|
-
import { MEMBERSHIPS_MS_UPDATE, MEMBERSHIPS_LS_UPDATE, PUSH_NON_RETRYABLE_ERROR, PUSH_SUBSYSTEM_DOWN, SECONDS_BEFORE_EXPIRATION, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE, RB_SEGMENT_UPDATE, PUSH_RETRYABLE_ERROR, PUSH_SUBSYSTEM_UP, ControlType } from './constants';
|
|
10
|
+
import { MEMBERSHIPS_MS_UPDATE, MEMBERSHIPS_LS_UPDATE, PUSH_NON_RETRYABLE_ERROR, PUSH_SUBSYSTEM_DOWN, SECONDS_BEFORE_EXPIRATION, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE, CONFIG_UPDATE, RB_SEGMENT_UPDATE, PUSH_RETRYABLE_ERROR, PUSH_SUBSYSTEM_UP, ControlType } from './constants';
|
|
12
11
|
import { STREAMING_FALLBACK, STREAMING_REFRESH_TOKEN, STREAMING_CONNECTING, STREAMING_DISABLED, ERROR_STREAMING_AUTH, STREAMING_DISCONNECTING, STREAMING_RECONNECT, STREAMING_PARSING_MEMBERSHIPS_UPDATE } from '../../logger/constants';
|
|
13
12
|
import { UpdateStrategy } from './SSEHandler/types';
|
|
14
13
|
import { getDelay, isInBitmap, parseBitmap, parseCompressedData } from './parseUtils';
|
|
@@ -34,7 +33,6 @@ export function pushManagerFactory(params, pollingManager) {
|
|
|
34
33
|
log.warn(STREAMING_FALLBACK, [e]);
|
|
35
34
|
return;
|
|
36
35
|
}
|
|
37
|
-
var authenticate = authenticateFactory(serviceApi.fetchAuth);
|
|
38
36
|
// init feedback loop
|
|
39
37
|
var pushEmitter = new platform.EventEmitter();
|
|
40
38
|
var sseHandler = SSEHandlerFactory(log, pushEmitter, telemetryTracker);
|
|
@@ -44,8 +42,6 @@ export function pushManagerFactory(params, pollingManager) {
|
|
|
44
42
|
var segmentsUpdateWorker = userKey ? undefined : SegmentsUpdateWorker(log, pollingManager.segmentsSyncTask, storage.segments);
|
|
45
43
|
// For server-side we pass the segmentsSyncTask, used by DefinitionsUpdateWorker to fetch new segments
|
|
46
44
|
var definitionsUpdateWorker = DefinitionsUpdateWorker(log, storage, pollingManager.definitionsSyncTask, readiness.definitions, telemetryTracker);
|
|
47
|
-
// [Only for client-side] map of hashes to user keys, to dispatch membership update events to the corresponding MySegmentsUpdateWorker
|
|
48
|
-
var userKeyHashes = {};
|
|
49
45
|
// [Only for client-side] map of user keys to their corresponding hash64 and MySegmentsUpdateWorkers.
|
|
50
46
|
// Hash64 is used to process membership update events and dispatch actions to the corresponding MySegmentsUpdateWorker.
|
|
51
47
|
var clients = {};
|
|
@@ -90,7 +86,7 @@ export function pushManagerFactory(params, pollingManager) {
|
|
|
90
86
|
log.info(STREAMING_CONNECTING);
|
|
91
87
|
disconnected = false;
|
|
92
88
|
var userKeys = userKey ? Object.keys(clients) : undefined;
|
|
93
|
-
|
|
89
|
+
serviceApi.fetchAuth(userKeys).then(function (authData) {
|
|
94
90
|
if (disconnected)
|
|
95
91
|
return;
|
|
96
92
|
// 'pushEnabled: false' is handled as a PUSH_NON_RETRYABLE_ERROR instead of PUSH_SUBSYSTEM_DOWN, in order to
|
|
@@ -178,8 +174,11 @@ export function pushManagerFactory(params, pollingManager) {
|
|
|
178
174
|
connectPush();
|
|
179
175
|
});
|
|
180
176
|
/** Functions related to synchronization (Queues and Workers in the spec) */
|
|
177
|
+
// Some notification types are specific to the Feature flags or Configs SDKs. Their handlers are
|
|
178
|
+
// registered unconditionally, since the SDK only subscribes to the channels of its own entities.
|
|
181
179
|
pushEmitter.on(SPLIT_KILL, definitionsUpdateWorker.killDefinition);
|
|
182
180
|
pushEmitter.on(SPLIT_UPDATE, definitionsUpdateWorker.put);
|
|
181
|
+
pushEmitter.on(CONFIG_UPDATE, definitionsUpdateWorker.put);
|
|
183
182
|
pushEmitter.on(RB_SEGMENT_UPDATE, definitionsUpdateWorker.put);
|
|
184
183
|
function handleMySegmentsUpdate(parsedData) {
|
|
185
184
|
switch (parsedData.u) {
|
|
@@ -282,9 +281,7 @@ export function pushManagerFactory(params, pollingManager) {
|
|
|
282
281
|
},
|
|
283
282
|
// [Only for client-side]
|
|
284
283
|
add: function (userKey, mySegmentsSyncTask) {
|
|
285
|
-
|
|
286
|
-
if (!userKeyHashes[hash]) {
|
|
287
|
-
userKeyHashes[hash] = userKey;
|
|
284
|
+
if (!clients[userKey]) {
|
|
288
285
|
clients[userKey] = {
|
|
289
286
|
hash64: hash64(userKey),
|
|
290
287
|
worker: MySegmentsUpdateWorker(log, storage, mySegmentsSyncTask, telemetryTracker)
|
|
@@ -305,8 +302,6 @@ export function pushManagerFactory(params, pollingManager) {
|
|
|
305
302
|
},
|
|
306
303
|
// [Only for client-side]
|
|
307
304
|
remove: function (userKey) {
|
|
308
|
-
var hash = hashUserKey(userKey);
|
|
309
|
-
delete userKeyHashes[hash];
|
|
310
305
|
delete clients[userKey];
|
|
311
306
|
}
|
|
312
307
|
});
|
|
@@ -20,7 +20,7 @@ export function fromImpressionsCollector(sendLabels, data) {
|
|
|
20
20
|
b: entry.bucketingKey,
|
|
21
21
|
pt: entry.pt,
|
|
22
22
|
properties: entry.properties,
|
|
23
|
-
// @
|
|
23
|
+
// @TODO: add entityType to public SplitIO.ImpressionDTO type
|
|
24
24
|
et: entry.entityType, // Definition type
|
|
25
25
|
};
|
|
26
26
|
})
|
|
@@ -5,8 +5,6 @@ import { isConsentGranted } from '../consent';
|
|
|
5
5
|
import { POLLING, STREAMING, SYNC_MODE_UPDATE } from '../utils/constants';
|
|
6
6
|
import { SDK_DEFINITIONS_CACHE_LOADED } from '../readiness/constants';
|
|
7
7
|
import { usesSegmentsSync } from '../storages/AbstractDefinitionsCacheSync';
|
|
8
|
-
import { splitChangesFetcherFactory } from './polling/fetchers/splitChangesFetcher';
|
|
9
|
-
import { segmentChangesFetcherFactory } from './polling/fetchers/segmentChangesFetcher';
|
|
10
8
|
/**
|
|
11
9
|
* Online SyncManager factory.
|
|
12
10
|
* Can be used for server-side API, and client-side API with or without multiple clients.
|
|
@@ -14,18 +12,15 @@ import { segmentChangesFetcherFactory } from './polling/fetchers/segmentChangesF
|
|
|
14
12
|
* @param pollingManagerFactory - allows to specialize the SyncManager for server-side or client-side API by passing
|
|
15
13
|
* `pollingManagerSSFactory` or `pollingManagerCSFactory` respectively.
|
|
16
14
|
* @param pushManagerFactory - optional to build a SyncManager with or without streaming support
|
|
17
|
-
* @param definitionChangesFetcherFactory - optional to replace the default split changes fetcher
|
|
18
15
|
*/
|
|
19
|
-
export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFactory
|
|
20
|
-
if (definitionFetcherFactory === void 0) { definitionFetcherFactory = splitChangesFetcherFactory; }
|
|
21
|
-
if (segmentFetcherFactory === void 0) { segmentFetcherFactory = function (params) { return segmentChangesFetcherFactory(params.serviceApi.fetchSegmentChanges); }; }
|
|
16
|
+
export function syncManagerOnlineFactory(pollingManagerFactory, pushManagerFactory) {
|
|
22
17
|
/**
|
|
23
18
|
* SyncManager factory for modular SDK
|
|
24
19
|
*/
|
|
25
20
|
return function (params) {
|
|
26
21
|
var settings = params.settings, _a = params.settings, log = _a.log, streamingEnabled = _a.streamingEnabled, syncEnabled = _a.sync.enabled, telemetryTracker = params.telemetryTracker, storage = params.storage, readiness = params.readiness;
|
|
27
22
|
/** Polling Manager */
|
|
28
|
-
var pollingManager = pollingManagerFactory && pollingManagerFactory(params
|
|
23
|
+
var pollingManager = pollingManagerFactory && pollingManagerFactory(params);
|
|
29
24
|
/** Push Manager */
|
|
30
25
|
var pushManager = syncEnabled && streamingEnabled && pollingManager && pushManagerFactory ?
|
|
31
26
|
pushManagerFactory(params, pollingManager) :
|
package/package.json
CHANGED
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
|
|
|
@@ -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,
|
|
@@ -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,20 @@ 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
16
|
export interface IAuthProvider {
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Returns the cached credential, or fetches a new one if there isn't one cached or it's expired,
|
|
19
|
+
* retrying with backoff on recoverable errors. Used by `secureSplitHttpClient` and `serviceApi.fetchAuth`.
|
|
20
|
+
*/
|
|
21
|
+
credential(): Promise<IJwtCredential>;
|
|
22
|
+
/**
|
|
23
|
+
* Invalidates/clears the cached credential. Used by `secureSplitHttpClient` in the special case of 401 error,
|
|
24
|
+
* and by `serviceApi.fetchAuth` to force a credential/token refresh.
|
|
25
|
+
*/
|
|
18
26
|
invalidate(): void;
|
|
19
27
|
stop(): void;
|
|
20
28
|
}
|
|
@@ -27,20 +35,18 @@ export function authProviderFactory(settings: ISettings, splitHttpClient: ISplit
|
|
|
27
35
|
|
|
28
36
|
const { urls, log } = settings;
|
|
29
37
|
|
|
30
|
-
|
|
38
|
+
const fetchAuth = fetchAuthFactory(() => {
|
|
31
39
|
let url = `${urls.auth}/api/v3/auth?capabilities=config,aiconfig`;
|
|
32
40
|
return splitHttpClient(url, undefined, telemetryTracker.trackHttp(TOKEN), false, true);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const authenticate = authenticateFactory(fetchAuth);
|
|
41
|
+
});
|
|
36
42
|
const backoff = new Backoff(fetchCredential);
|
|
37
43
|
|
|
38
|
-
let cachedCredential:
|
|
39
|
-
let inFlightPromise: Promise<
|
|
44
|
+
let cachedCredential: IJwtCredential | undefined;
|
|
45
|
+
let inFlightPromise: Promise<IJwtCredential> | undefined;
|
|
40
46
|
let stopped = false;
|
|
41
47
|
|
|
42
|
-
function fetchCredential(): Promise<
|
|
43
|
-
return
|
|
48
|
+
function fetchCredential(): Promise<IJwtCredential> {
|
|
49
|
+
return fetchAuth().then((credential: IJwtCredential) => {
|
|
44
50
|
log.info(LOG_PREFIX_SYNC_AUTH + 'credential fetched successfully');
|
|
45
51
|
cachedCredential = credential;
|
|
46
52
|
inFlightPromise = undefined;
|
|
@@ -62,7 +68,7 @@ export function authProviderFactory(settings: ISettings, splitHttpClient: ISplit
|
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
return {
|
|
65
|
-
credential(): Promise<
|
|
71
|
+
credential(): Promise<IJwtCredential> {
|
|
66
72
|
if (cachedCredential && !isExpired(cachedCredential)) {
|
|
67
73
|
return Promise.resolve(cachedCredential);
|
|
68
74
|
}
|
|
@@ -1,22 +1,14 @@
|
|
|
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(
|
|
17
|
-
|
|
18
|
-
const splitHttpClient = splitHttpClientFactory(settings, platform);
|
|
19
|
-
const authProvider = authProviderFactory(settings, splitHttpClient, telemetryTracker);
|
|
11
|
+
export function secureSplitHttpClientFactory(splitHttpClient: ISplitHttpClient, authProvider: IAuthProvider): ISecureSplitHttpClient {
|
|
20
12
|
|
|
21
13
|
function makeRequest(url: string, options: IRequestOptions | undefined, latencyTracker: ((error?: NetworkError) => void) | undefined, logErrorsAsInfo: boolean | undefined, token: string): Promise<IResponse> {
|
|
22
14
|
return splitHttpClient(url, { ...options, headers: { ...options?.headers, Authorization: `Bearer ${token}` } }, latencyTracker, logErrorsAsInfo, true);
|
|
@@ -1,40 +1,52 @@
|
|
|
1
1
|
import { IPlatform } from '../sdkFactory/types';
|
|
2
2
|
import { ISettings } from '../types';
|
|
3
3
|
import { splitHttpClientFactory } from './splitHttpClient';
|
|
4
|
-
import {
|
|
4
|
+
import { IServiceApi } from './types';
|
|
5
5
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
6
6
|
import { ITelemetryTracker } from '../trackers/types';
|
|
7
|
-
import { SPLITS, IMPRESSIONS, IMPRESSIONS_COUNT, EVENTS, TELEMETRY,
|
|
7
|
+
import { SPLITS, IMPRESSIONS, IMPRESSIONS_COUNT, EVENTS, TELEMETRY, SEGMENT, MEMBERSHIPS } from '../utils/constants';
|
|
8
8
|
import { ERROR_TOO_MANY_SETS } from '../logger/constants';
|
|
9
|
+
import { authProviderFactory } from './authProvider';
|
|
10
|
+
import { secureSplitHttpClientFactory } from './secureSplitHttpClient';
|
|
9
11
|
|
|
10
12
|
const noCacheHeaderOptions = { headers: { 'Cache-Control': 'no-cache' } };
|
|
11
13
|
|
|
12
|
-
function userKeyToQueryParam(userKey: string) {
|
|
13
|
-
return 'users=' + encodeURIComponent(userKey); // no need to check availability of `encodeURIComponent`, since it is a global highly supported.
|
|
14
|
-
}
|
|
15
|
-
|
|
16
14
|
/**
|
|
17
15
|
* Factory of ServiceApi objects, which group the collection of HTTP endpoints used by the SDKs
|
|
18
16
|
*
|
|
19
17
|
* @param settings - validated settings object
|
|
20
18
|
* @param platform - object containing environment-specific dependencies
|
|
21
19
|
* @param telemetryTracker - telemetry tracker
|
|
22
|
-
* @param secureSplitHttpClientFactory - factory of SecureSplitHttpClient objects
|
|
23
20
|
*/
|
|
24
21
|
export function serviceApiFactory(
|
|
25
22
|
settings: ISettings,
|
|
26
23
|
platform: Pick<IPlatform, 'getOptions' | 'getFetch'>,
|
|
27
24
|
telemetryTracker: ITelemetryTracker,
|
|
28
|
-
secureSplitHttpClientFactory?: (settings: ISettings, platform: Pick<IPlatform, 'getOptions' | 'getFetch'>, telemetryTracker: ITelemetryTracker) => ISecureSplitHttpClient,
|
|
29
25
|
): IServiceApi {
|
|
30
26
|
|
|
31
27
|
const urls = settings.urls;
|
|
32
28
|
const filterQueryString = settings.sync.__splitFiltersValidation && settings.sync.__splitFiltersValidation.queryString;
|
|
33
29
|
const SplitSDKImpressionsMode = settings.sync.impressionsMode;
|
|
30
|
+
|
|
34
31
|
const splitHttpClient = splitHttpClientFactory(settings, platform);
|
|
35
|
-
|
|
32
|
+
|
|
33
|
+
// Shared authProvider so that the SSE authentication (via `fetchAuth`,
|
|
34
|
+
// used by `pushManager`) and every authenticated HTTP request to the Configs
|
|
35
|
+
// API reuse the same cached credential instead of each fetching their own.
|
|
36
|
+
const authProvider = authProviderFactory(settings, splitHttpClient, telemetryTracker);
|
|
37
|
+
const secureSplitHttpClient = secureSplitHttpClientFactory(splitHttpClient, authProvider);
|
|
38
|
+
|
|
39
|
+
let initialAuth = true;
|
|
36
40
|
|
|
37
41
|
return {
|
|
42
|
+
fetchAuth() {
|
|
43
|
+
// Guard condition to avoid invalidating the cached credential on the first pushManager authentication
|
|
44
|
+
if (initialAuth) initialAuth = false;
|
|
45
|
+
else authProvider.invalidate();
|
|
46
|
+
|
|
47
|
+
return authProvider.credential();
|
|
48
|
+
},
|
|
49
|
+
|
|
38
50
|
// @TODO throw errors if health check requests fail, to log them in the Synchronizer
|
|
39
51
|
getSdkAPIHealthCheck() {
|
|
40
52
|
const url = `${urls.sdk}/api/version`;
|
|
@@ -46,15 +58,6 @@ export function serviceApiFactory(
|
|
|
46
58
|
return splitHttpClient(url).then(() => true).catch(() => false);
|
|
47
59
|
},
|
|
48
60
|
|
|
49
|
-
fetchAuth(userMatchingKeys?: string[]) {
|
|
50
|
-
let url = `${urls.auth}/api/v2/auth?s=${settings.sync.flagSpecVersion}`;
|
|
51
|
-
if (userMatchingKeys) { // `userMatchingKeys` is undefined in server-side
|
|
52
|
-
const queryParams = userMatchingKeys.map(userKeyToQueryParam).join('&');
|
|
53
|
-
if (queryParams) url += '&' + queryParams;
|
|
54
|
-
}
|
|
55
|
-
return splitHttpClient(url, undefined, telemetryTracker.trackHttp(TOKEN));
|
|
56
|
-
},
|
|
57
|
-
|
|
58
61
|
fetchSplitChanges(since: number, noCache?: boolean, till?: number, rbSince?: number) {
|
|
59
62
|
const url = `${urls.sdk}/api/splitChanges?s=${settings.sync.flagSpecVersion}&since=${since}${rbSince ? '&rbSince=' + rbSince : ''}${filterQueryString || ''}${till ? '&till=' + till : ''}`;
|
|
60
63
|
return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined, telemetryTracker.trackHttp(SPLITS))
|
package/src/services/splitApi.ts
CHANGED
|
@@ -6,6 +6,7 @@ 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';
|
|
8
8
|
import { ERROR_TOO_MANY_SETS } from '../logger/constants';
|
|
9
|
+
import { fetchAuthFactory } from '../sync/streaming/AuthClient';
|
|
9
10
|
|
|
10
11
|
const noCacheHeaderOptions = { headers: { 'Cache-Control': 'no-cache' } };
|
|
11
12
|
|
|
@@ -33,6 +34,15 @@ export function splitApiFactory(
|
|
|
33
34
|
const splitHttpClient = splitHttpClientFactory(settings, platform);
|
|
34
35
|
|
|
35
36
|
return {
|
|
37
|
+
fetchAuth: fetchAuthFactory((userMatchingKeys?: string[]) => {
|
|
38
|
+
let url = `${urls.auth}/v2/auth?s=${settings.sync.flagSpecVersion}`;
|
|
39
|
+
if (userMatchingKeys) { // `userMatchingKeys` is undefined in server-side
|
|
40
|
+
const queryParams = userMatchingKeys.map(userKeyToQueryParam).join('&');
|
|
41
|
+
if (queryParams) url += '&' + queryParams;
|
|
42
|
+
}
|
|
43
|
+
return splitHttpClient(url, undefined, telemetryTracker.trackHttp(TOKEN));
|
|
44
|
+
}),
|
|
45
|
+
|
|
36
46
|
// @TODO throw errors if health check requests fail, to log them in the Synchronizer
|
|
37
47
|
getSdkAPIHealthCheck() {
|
|
38
48
|
const url = `${urls.sdk}/version`;
|
|
@@ -44,15 +54,6 @@ export function splitApiFactory(
|
|
|
44
54
|
return splitHttpClient(url).then(() => true).catch(() => false);
|
|
45
55
|
},
|
|
46
56
|
|
|
47
|
-
fetchAuth(userMatchingKeys?: string[]) {
|
|
48
|
-
let url = `${urls.auth}/v2/auth?s=${settings.sync.flagSpecVersion}`;
|
|
49
|
-
if (userMatchingKeys) { // `userMatchingKeys` is undefined in server-side
|
|
50
|
-
const queryParams = userMatchingKeys.map(userKeyToQueryParam).join('&');
|
|
51
|
-
if (queryParams) url += '&' + queryParams;
|
|
52
|
-
}
|
|
53
|
-
return splitHttpClient(url, undefined, telemetryTracker.trackHttp(TOKEN));
|
|
54
|
-
},
|
|
55
|
-
|
|
56
57
|
fetchSplitChanges(since: number, noCache?: boolean, till?: number, rbSince?: number) {
|
|
57
58
|
const url = `${urls.sdk}/splitChanges?s=${settings.sync.flagSpecVersion}&since=${since}${rbSince ? '&rbSince=' + rbSince : ''}${filterQueryString || ''}${till ? '&till=' + till : ''}`;
|
|
58
59
|
return splitHttpClient(url, noCache ? noCacheHeaderOptions : undefined, telemetryTracker.trackHttp(SPLITS))
|
package/src/services/types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { IFetchAuth } from '../sync/streaming/AuthClient/types';
|
|
2
|
+
|
|
1
3
|
export type IRequestOptions = {
|
|
2
4
|
method?: string,
|
|
3
5
|
headers?: Record<string, string>,
|
|
@@ -35,8 +37,6 @@ export type ISplitHttpClient = (url: string, options?: IRequestOptions, latencyT
|
|
|
35
37
|
|
|
36
38
|
export type ISecureSplitHttpClient = ISplitHttpClient & { stop(): void }
|
|
37
39
|
|
|
38
|
-
export type IFetchAuth = (userKeys?: string[]) => Promise<IResponse>
|
|
39
|
-
|
|
40
40
|
export type IFetchDefinitionChanges = (since: number, noCache?: boolean, till?: number, rbSince?: number) => Promise<IResponse>
|
|
41
41
|
|
|
42
42
|
export type IFetchSegmentChanges = (since: number, segmentName: string, noCache?: boolean, till?: number) => Promise<IResponse>
|
|
@@ -10,110 +10,112 @@ import { POLLING_SMART_PAUSING, POLLING_START, POLLING_STOP } from '../../logger
|
|
|
10
10
|
import { ISdkFactoryContextSync } from '../../sdkFactory/types';
|
|
11
11
|
import { usesSegmentsSync } from '../../storages/AbstractDefinitionsCacheSync';
|
|
12
12
|
import { SdkUpdateMetadata } from '../../../types/splitio';
|
|
13
|
-
import {
|
|
13
|
+
import { splitChangesFetcherFactory } from './fetchers/splitChangesFetcher';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Expose start / stop mechanism for polling data from services.
|
|
17
17
|
* For client-side API with multiple clients.
|
|
18
18
|
*/
|
|
19
19
|
export function pollingManagerCSFactory(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
): IPollingManagerCS {
|
|
20
|
+
definitionFetcherFactory = splitChangesFetcherFactory,
|
|
21
|
+
): (params: ISdkFactoryContextSync) => IPollingManagerCS {
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
return function (params: ISdkFactoryContextSync): IPollingManagerCS {
|
|
24
|
+
const { serviceApi, storage, readiness, settings } = params;
|
|
25
|
+
const log = settings.log;
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
const definitionChangesFetcher = definitionFetcherFactory(params);
|
|
28
|
+
const definitionsSyncTask = definitionsSyncTaskFactory(definitionChangesFetcher, storage, readiness, settings);
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
// Map of matching keys to their corresponding MySegmentsSyncTask.
|
|
31
|
+
const mySegmentsSyncTasks: Record<string, IMySegmentsSyncTask> = {};
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
const matchingKey = getMatching(settings.core.key);
|
|
34
|
+
const mySegmentsSyncTask = add(matchingKey, readiness, storage);
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
function startMySegmentsSyncTasks() {
|
|
37
|
+
forOwn(mySegmentsSyncTasks, (mySegmentsSyncTask) => {
|
|
38
|
+
mySegmentsSyncTask.start();
|
|
39
|
+
});
|
|
40
|
+
}
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// smart pausing
|
|
48
|
-
readiness.definitions.on(SDK_DEFINITIONS_ARRIVED, () => {
|
|
49
|
-
if (!definitionsSyncTask.isRunning()) return; // noop if not doing polling
|
|
50
|
-
const usingSegments = usesSegmentsSync(storage);
|
|
51
|
-
if (usingSegments !== mySegmentsSyncTask.isRunning()) {
|
|
52
|
-
log.info(POLLING_SMART_PAUSING, [usingSegments ? 'ON' : 'OFF']);
|
|
53
|
-
if (usingSegments) {
|
|
54
|
-
startMySegmentsSyncTasks();
|
|
55
|
-
} else {
|
|
56
|
-
stopMySegmentsSyncTasks();
|
|
57
|
-
}
|
|
42
|
+
function stopMySegmentsSyncTasks() {
|
|
43
|
+
forOwn(mySegmentsSyncTasks, (mySegmentsSyncTask) => {
|
|
44
|
+
if (mySegmentsSyncTask.isRunning()) mySegmentsSyncTask.stop();
|
|
45
|
+
});
|
|
58
46
|
}
|
|
59
|
-
});
|
|
60
47
|
|
|
61
|
-
|
|
62
|
-
|
|
48
|
+
// smart pausing
|
|
49
|
+
readiness.definitions.on(SDK_DEFINITIONS_ARRIVED, () => {
|
|
50
|
+
if (!definitionsSyncTask.isRunning()) return; // noop if not doing polling
|
|
51
|
+
const usingSegments = usesSegmentsSync(storage);
|
|
52
|
+
if (usingSegments !== mySegmentsSyncTask.isRunning()) {
|
|
53
|
+
log.info(POLLING_SMART_PAUSING, [usingSegments ? 'ON' : 'OFF']);
|
|
54
|
+
if (usingSegments) {
|
|
55
|
+
startMySegmentsSyncTasks();
|
|
56
|
+
} else {
|
|
57
|
+
stopMySegmentsSyncTasks();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
function add(matchingKey: string, readiness: IReadinessManager, storage: IStorageSync) {
|
|
63
|
+
const mySegmentsSyncTask = mySegmentsSyncTaskFactory(serviceApi.fetchMemberships, storage, readiness, settings, matchingKey);
|
|
63
64
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
// smart ready
|
|
66
|
+
function smartReady(metadata: SdkUpdateMetadata) {
|
|
67
|
+
if (!readiness.isReady() && !usesSegmentsSync(storage)) readiness.segments.emit(SDK_SEGMENTS_ARRIVED, metadata);
|
|
68
|
+
}
|
|
69
|
+
if (!usesSegmentsSync(storage)) setTimeout(smartReady, 0);
|
|
70
|
+
else readiness.definitions.once(SDK_DEFINITIONS_ARRIVED, smartReady);
|
|
71
|
+
|
|
72
|
+
mySegmentsSyncTasks[matchingKey] = mySegmentsSyncTask;
|
|
73
|
+
return mySegmentsSyncTask;
|
|
67
74
|
}
|
|
68
|
-
if (!usesSegmentsSync(storage)) setTimeout(smartReady, 0);
|
|
69
|
-
else readiness.definitions.once(SDK_DEFINITIONS_ARRIVED, smartReady);
|
|
70
75
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
76
|
+
return {
|
|
77
|
+
definitionsSyncTask,
|
|
78
|
+
segmentsSyncTask: mySegmentsSyncTask,
|
|
74
79
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
80
|
+
// Start periodic fetching (polling)
|
|
81
|
+
start() {
|
|
82
|
+
log.info(POLLING_START);
|
|
78
83
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
definitionsSyncTask.start();
|
|
85
|
+
if (usesSegmentsSync(storage)) startMySegmentsSyncTasks();
|
|
86
|
+
},
|
|
82
87
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
// Stop periodic fetching (polling)
|
|
89
|
+
stop() {
|
|
90
|
+
log.info(POLLING_STOP);
|
|
86
91
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
92
|
+
if (definitionsSyncTask.isRunning()) definitionsSyncTask.stop();
|
|
93
|
+
stopMySegmentsSyncTasks();
|
|
94
|
+
},
|
|
90
95
|
|
|
91
|
-
if
|
|
92
|
-
|
|
93
|
-
},
|
|
96
|
+
// Used by SyncManager to know if running in polling mode.
|
|
97
|
+
isRunning: definitionsSyncTask.isRunning,
|
|
94
98
|
|
|
95
|
-
|
|
96
|
-
|
|
99
|
+
// fetch definitions and segments
|
|
100
|
+
syncAll() {
|
|
101
|
+
const promises = [definitionsSyncTask.execute()];
|
|
102
|
+
forOwn(mySegmentsSyncTasks, (mySegmentsSyncTask) => {
|
|
103
|
+
promises.push(mySegmentsSyncTask.execute());
|
|
104
|
+
});
|
|
105
|
+
return Promise.all(promises);
|
|
106
|
+
},
|
|
97
107
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
const promises = [definitionsSyncTask.execute()];
|
|
101
|
-
forOwn(mySegmentsSyncTasks, (mySegmentsSyncTask) => {
|
|
102
|
-
promises.push(mySegmentsSyncTask.execute());
|
|
103
|
-
});
|
|
104
|
-
return Promise.all(promises);
|
|
105
|
-
},
|
|
108
|
+
// Support for handling mySegments sync of multiple clients
|
|
109
|
+
add,
|
|
106
110
|
|
|
107
|
-
|
|
108
|
-
|
|
111
|
+
remove(matchingKey: string) {
|
|
112
|
+
delete mySegmentsSyncTasks[matchingKey];
|
|
113
|
+
},
|
|
109
114
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
115
|
+
get(matchingKey: string) {
|
|
116
|
+
return mySegmentsSyncTasks[matchingKey];
|
|
117
|
+
}
|
|
118
|
+
};
|
|
113
119
|
|
|
114
|
-
get(matchingKey: string) {
|
|
115
|
-
return mySegmentsSyncTasks[matchingKey];
|
|
116
|
-
}
|
|
117
120
|
};
|
|
118
|
-
|
|
119
121
|
}
|