@splitsoftware/splitio-commons 3.1.1-rc.4 → 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 +2 -2
- 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/updaters/definitionChangesUpdater.js +2 -2
- 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/esm/presets/serverSide.js +2 -2
- 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/updaters/definitionChangesUpdater.js +3 -3
- 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/package.json +1 -1
- package/src/presets/serverSide.ts +2 -2
- 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/updaters/definitionChangesUpdater.ts +3 -3
- 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
|
@@ -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
|
});
|
package/package.json
CHANGED
|
@@ -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';
|
|
@@ -11,7 +11,7 @@ const syncManagerOnlineSSFactory = syncManagerOnlineFactory(pollingManagerSSFact
|
|
|
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>
|
|
@@ -9,7 +9,7 @@ import { SYNC_FETCH, SYNC_UPDATE, SYNC_FETCH_FAILS, SYNC_FETCH_RETRY } from '../
|
|
|
9
9
|
import { startsWith } from '../../../utils/lang';
|
|
10
10
|
import { IN_RULE_BASED_SEGMENT, IN_SEGMENT, RULE_BASED_SEGMENT, STANDARD_SEGMENT } from '../../../utils/constants';
|
|
11
11
|
import { setToArray } from '../../../utils/lang/sets';
|
|
12
|
-
import {
|
|
12
|
+
import { RB_SEGMENT_UPDATE } from '../../streaming/constants';
|
|
13
13
|
import { SdkUpdateMetadata } from '../../../../types/splitio';
|
|
14
14
|
import { ISplit } from '../fetchers/splitChangesFetcher';
|
|
15
15
|
import { ISegmentsSyncTask } from '../types';
|
|
@@ -154,14 +154,14 @@ export function definitionChangesUpdaterFactory(
|
|
|
154
154
|
|
|
155
155
|
return Promise.resolve(
|
|
156
156
|
instantUpdate ?
|
|
157
|
-
instantUpdate.type ===
|
|
157
|
+
instantUpdate.type === RB_SEGMENT_UPDATE ?
|
|
158
|
+
{ rbs: convertInstantUpdateToDefinitionChanges(instantUpdate) as IDefinitionChangesResponse['rbs'] } :
|
|
158
159
|
// IFFU edge case: a change to definition that adds an IN_RULE_BASED_SEGMENT matcher that is not present yet
|
|
159
160
|
Promise.resolve(rbSegments.contains(parseSegments(instantUpdate.payload, IN_RULE_BASED_SEGMENT))).then((contains) => {
|
|
160
161
|
return contains ?
|
|
161
162
|
{ d: convertInstantUpdateToDefinitionChanges(instantUpdate) as IDefinitionChangesResponse['d'] } :
|
|
162
163
|
definitionChangesFetcher(since, noCache, till, rbSince, _promiseDecorator);
|
|
163
164
|
}) :
|
|
164
|
-
{ rbs: convertInstantUpdateToDefinitionChanges(instantUpdate) as IDefinitionChangesResponse['rbs'] } :
|
|
165
165
|
definitionChangesFetcher(since, noCache, till, rbSince, _promiseDecorator)
|
|
166
166
|
)
|
|
167
167
|
.then((definitionChanges: IDefinitionChangesResponse) => {
|
|
@@ -1,22 +1,20 @@
|
|
|
1
|
-
import { IFetchAuth } from '
|
|
2
|
-
import { IAuthenticate, IJwtCredentialV2 } from './types';
|
|
1
|
+
import { IJwtCredential, IFetchAuth } from './types';
|
|
3
2
|
import { objectAssign } from '../../../utils/lang/objectAssign';
|
|
4
|
-
import { encodeToBase64 } from '../../../utils/base64';
|
|
5
3
|
import { decodeJWTtoken } from '../../../utils/jwt';
|
|
6
|
-
import {
|
|
4
|
+
import { IResponse } from '../../../services/types';
|
|
7
5
|
|
|
8
6
|
/**
|
|
9
7
|
* Factory of authentication function.
|
|
10
8
|
*
|
|
11
|
-
* @param fetchAuth -
|
|
9
|
+
* @param fetchAuth - /auth endpoint
|
|
12
10
|
*/
|
|
13
|
-
export function
|
|
11
|
+
export function fetchAuthFactory(fetchAuth: (userKeys?: string[]) => Promise<IResponse>): IFetchAuth {
|
|
14
12
|
|
|
15
13
|
/**
|
|
16
14
|
* Run authentication requests to Auth Server, and returns a promise that resolves with the decoded JTW token.
|
|
17
15
|
* @param userKeys - set of user Keys to track membership updates. It is undefined for server-side API.
|
|
18
16
|
*/
|
|
19
|
-
return function authenticate(userKeys?: string[]): Promise<
|
|
17
|
+
return function authenticate(userKeys?: string[]): Promise<IJwtCredential> {
|
|
20
18
|
return fetchAuth(userKeys)
|
|
21
19
|
.then(resp => resp.json())
|
|
22
20
|
.then(json => {
|
|
@@ -24,19 +22,20 @@ export function authenticateFactory(fetchAuth: IFetchAuth): IAuthenticate {
|
|
|
24
22
|
const decodedToken = decodeJWTtoken(json.token);
|
|
25
23
|
if (typeof decodedToken.iat !== 'number' || typeof decodedToken.exp !== 'number') throw new Error('token properties "issuedAt" (iat) or "expiration" (exp) are missing or invalid');
|
|
26
24
|
const channels = JSON.parse(decodedToken['x-ably-capability']);
|
|
27
|
-
|
|
25
|
+
const credential = objectAssign({
|
|
28
26
|
decodedToken,
|
|
29
27
|
channels
|
|
30
28
|
}, json);
|
|
29
|
+
// The `/api/v3/auth` endpoint nests the streaming settings under `config.streaming`. Normalize
|
|
30
|
+
// them into the flat `pushEnabled`/`connDelay` properties used by the PushManager.
|
|
31
|
+
const streaming = credential.config && credential.config.streaming;
|
|
32
|
+
if (streaming) {
|
|
33
|
+
credential.pushEnabled = streaming.enabled;
|
|
34
|
+
credential.connDelay = streaming.delay;
|
|
35
|
+
}
|
|
36
|
+
return credential;
|
|
31
37
|
}
|
|
32
38
|
return json;
|
|
33
39
|
});
|
|
34
40
|
};
|
|
35
41
|
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Returns the hash of a given user key
|
|
39
|
-
*/
|
|
40
|
-
export function hashUserKey(userKey: string): string {
|
|
41
|
-
return encodeToBase64(hash(userKey, 0).toString());
|
|
42
|
-
}
|
|
@@ -1,23 +1,19 @@
|
|
|
1
1
|
import { IDecodedJWTToken } from '../../../utils/jwt/types';
|
|
2
2
|
|
|
3
|
-
export type
|
|
4
|
-
pushEnabled:
|
|
5
|
-
token: string // empty string ("") when `"pushEnabled": false`
|
|
6
|
-
decodedToken: IDecodedJWTToken
|
|
7
|
-
channels: { [channel: string]: string[] }
|
|
8
|
-
connDelay?: number
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export type IJwtCredentialV3 = {
|
|
12
|
-
token: string
|
|
3
|
+
export type IJwtCredential = {
|
|
4
|
+
token: string; // empty string ("") when `"pushEnabled": false`
|
|
13
5
|
decodedToken: IDecodedJWTToken
|
|
14
6
|
channels: { [channel: string]: string[] }
|
|
7
|
+
// /api/v2/auth fields
|
|
8
|
+
pushEnabled?: boolean | null;
|
|
9
|
+
connDelay?: number | null;
|
|
10
|
+
// /api/v3/auth fields
|
|
15
11
|
config?: {
|
|
16
12
|
streaming?: {
|
|
17
|
-
delay?: number
|
|
18
|
-
enabled?: boolean
|
|
13
|
+
delay?: number | null;
|
|
14
|
+
enabled?: boolean | null;
|
|
19
15
|
} | null;
|
|
20
16
|
} | null;
|
|
21
17
|
}
|
|
22
18
|
|
|
23
|
-
export type
|
|
19
|
+
export type IFetchAuth = (userKeys?: string[]) => Promise<IJwtCredential>
|
|
@@ -5,7 +5,7 @@ import { ISettings } from '../../../types';
|
|
|
5
5
|
import { checkIfServerSide } from '../../../utils/key';
|
|
6
6
|
import { isString } from '../../../utils/lang';
|
|
7
7
|
import { objectAssign } from '../../../utils/lang/objectAssign';
|
|
8
|
-
import {
|
|
8
|
+
import { IJwtCredential } from '../AuthClient/types';
|
|
9
9
|
import { ISSEClient, ISseEventHandler } from './types';
|
|
10
10
|
|
|
11
11
|
const ABLY_API_VERSION = '1.1';
|
|
@@ -66,7 +66,7 @@ export class SSEClient implements ISSEClient {
|
|
|
66
66
|
/**
|
|
67
67
|
* Open the connection with a given authToken
|
|
68
68
|
*/
|
|
69
|
-
open(authToken:
|
|
69
|
+
open(authToken: IJwtCredential) {
|
|
70
70
|
this.close(); // it closes connection if previously opened
|
|
71
71
|
|
|
72
72
|
const channelsQueryParam = Object.keys(authToken.channels).map((channel) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { IJwtCredential } from '../AuthClient/types';
|
|
2
2
|
|
|
3
3
|
export interface ISseEventHandler {
|
|
4
4
|
handleError: (ev: Event) => any;
|
|
@@ -7,7 +7,7 @@ export interface ISseEventHandler {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
export interface ISSEClient {
|
|
10
|
-
open(authToken:
|
|
10
|
+
open(authToken: IJwtCredential): void,
|
|
11
11
|
close(): void,
|
|
12
12
|
setEventHandler(handler: ISseEventHandler): void
|
|
13
13
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { errorParser, messageParser } from './NotificationParser';
|
|
2
2
|
import { notificationKeeperFactory } from './NotificationKeeper';
|
|
3
|
-
import { PUSH_RETRYABLE_ERROR, PUSH_NON_RETRYABLE_ERROR, OCCUPANCY, CONTROL, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE, MEMBERSHIPS_MS_UPDATE, MEMBERSHIPS_LS_UPDATE, RB_SEGMENT_UPDATE } from '../constants';
|
|
3
|
+
import { PUSH_RETRYABLE_ERROR, PUSH_NON_RETRYABLE_ERROR, OCCUPANCY, CONTROL, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE, CONFIG_UPDATE, MEMBERSHIPS_MS_UPDATE, MEMBERSHIPS_LS_UPDATE, RB_SEGMENT_UPDATE } from '../constants';
|
|
4
4
|
import { IPushEventEmitter } from '../types';
|
|
5
5
|
import { ISseEventHandler } from '../SSEClient/types';
|
|
6
6
|
import { INotificationError, INotificationMessage } from './types';
|
|
@@ -80,6 +80,7 @@ export function SSEHandlerFactory(log: ILogger, pushEmitter: IPushEventEmitter,
|
|
|
80
80
|
switch (parsedData.type) {
|
|
81
81
|
/* update events */
|
|
82
82
|
case SPLIT_UPDATE:
|
|
83
|
+
case CONFIG_UPDATE:
|
|
83
84
|
case SEGMENT_UPDATE:
|
|
84
85
|
case MEMBERSHIPS_MS_UPDATE:
|
|
85
86
|
case MEMBERSHIPS_LS_UPDATE:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ControlType } from '../constants';
|
|
2
|
-
import { SEGMENT_UPDATE, SPLIT_UPDATE, SPLIT_KILL, CONTROL, OCCUPANCY, MEMBERSHIPS_LS_UPDATE, MEMBERSHIPS_MS_UPDATE, RB_SEGMENT_UPDATE } from '../types';
|
|
2
|
+
import { SEGMENT_UPDATE, SPLIT_UPDATE, CONFIG_UPDATE, SPLIT_KILL, CONTROL, OCCUPANCY, MEMBERSHIPS_LS_UPDATE, MEMBERSHIPS_MS_UPDATE, RB_SEGMENT_UPDATE } from '../types';
|
|
3
3
|
|
|
4
4
|
export enum Compression {
|
|
5
5
|
None = 0,
|
|
@@ -42,7 +42,7 @@ export interface ISegmentUpdateData {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
export interface ISplitUpdateData {
|
|
45
|
-
type: SPLIT_UPDATE | RB_SEGMENT_UPDATE,
|
|
45
|
+
type: SPLIT_UPDATE | CONFIG_UPDATE | RB_SEGMENT_UPDATE,
|
|
46
46
|
changeNumber: number,
|
|
47
47
|
pcn?: number,
|
|
48
48
|
d?: string,
|
|
@@ -30,6 +30,7 @@ export const MEMBERSHIPS_LS_UPDATE = 'MEMBERSHIPS_LS_UPDATE';
|
|
|
30
30
|
export const SEGMENT_UPDATE = 'SEGMENT_UPDATE';
|
|
31
31
|
export const SPLIT_KILL = 'SPLIT_KILL';
|
|
32
32
|
export const SPLIT_UPDATE = 'SPLIT_UPDATE';
|
|
33
|
+
export const CONFIG_UPDATE = 'CONFIG_UPDATE';
|
|
33
34
|
export const RB_SEGMENT_UPDATE = 'RB_SEGMENT_UPDATE';
|
|
34
35
|
|
|
35
36
|
// Control-type push notifications, handled by NotificationKeeper
|