@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
|
@@ -3,57 +3,61 @@ import { segmentsSyncTaskFactory } from './syncTasks/segmentsSyncTask';
|
|
|
3
3
|
import { IPollingManager, ISegmentsSyncTask, IDefinitionsSyncTask } from './types';
|
|
4
4
|
import { POLLING_START, POLLING_STOP, LOG_PREFIX_SYNC_POLLING } from '../../logger/constants';
|
|
5
5
|
import { ISdkFactoryContextSync } from '../../sdkFactory/types';
|
|
6
|
-
import {
|
|
6
|
+
import { segmentChangesFetcherFactory } from './fetchers/segmentChangesFetcher';
|
|
7
|
+
import { splitChangesFetcherFactory } from './fetchers/splitChangesFetcher';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Expose start / stop mechanism for pulling data from services.
|
|
10
11
|
*/
|
|
11
12
|
export function pollingManagerSSFactory(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
startingUp.
|
|
36
|
-
|
|
13
|
+
definitionFetcherFactory = splitChangesFetcherFactory,
|
|
14
|
+
segmentFetcherFactory = (params: ISdkFactoryContextSync) => segmentChangesFetcherFactory(params.serviceApi.fetchSegmentChanges)
|
|
15
|
+
): (params: ISdkFactoryContextSync) => IPollingManager {
|
|
16
|
+
|
|
17
|
+
return function (params: ISdkFactoryContextSync): IPollingManager {
|
|
18
|
+
const { storage, readiness, settings } = params;
|
|
19
|
+
const log = settings.log;
|
|
20
|
+
|
|
21
|
+
const definitionChangesFetcher = definitionFetcherFactory(params);
|
|
22
|
+
const segmentChangesFetcher = segmentFetcherFactory(params);
|
|
23
|
+
const segmentsSyncTask: ISegmentsSyncTask = segmentsSyncTaskFactory(segmentChangesFetcher, storage, readiness, settings);
|
|
24
|
+
const definitionsSyncTask: IDefinitionsSyncTask = definitionsSyncTaskFactory(definitionChangesFetcher, storage, readiness, settings, segmentsSyncTask);
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
definitionsSyncTask,
|
|
28
|
+
segmentsSyncTask,
|
|
29
|
+
|
|
30
|
+
// Start periodic fetching (polling)
|
|
31
|
+
start() {
|
|
32
|
+
log.info(POLLING_START);
|
|
33
|
+
log.debug(LOG_PREFIX_SYNC_POLLING + `${definitionChangesFetcher.type} will be refreshed each ${settings.scheduler.featuresRefreshRate} millis`);
|
|
34
|
+
log.debug(LOG_PREFIX_SYNC_POLLING + `segments will be refreshed each ${settings.scheduler.segmentsRefreshRate} millis`);
|
|
35
|
+
|
|
36
|
+
const startingUp = definitionsSyncTask.start();
|
|
37
|
+
if (startingUp) {
|
|
38
|
+
startingUp.then(() => {
|
|
39
|
+
if (definitionsSyncTask.isRunning()) segmentsSyncTask.start();
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
// Stop periodic fetching (polling)
|
|
45
|
+
stop() {
|
|
46
|
+
log.info(POLLING_STOP);
|
|
47
|
+
|
|
48
|
+
if (definitionsSyncTask.isRunning()) definitionsSyncTask.stop();
|
|
49
|
+
if (segmentsSyncTask.isRunning()) segmentsSyncTask.stop();
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// Used by SyncManager to know if running in polling mode.
|
|
53
|
+
isRunning: definitionsSyncTask.isRunning,
|
|
54
|
+
|
|
55
|
+
syncAll() {
|
|
56
|
+
// fetch definitions and segments. There is no need to catch this promise (`DefinitionChangesUpdater` is always resolved with a boolean value)
|
|
57
|
+
return definitionsSyncTask.execute().then(() => {
|
|
58
|
+
return segmentsSyncTask.execute();
|
|
37
59
|
});
|
|
38
60
|
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Stop periodic fetching (polling)
|
|
42
|
-
stop() {
|
|
43
|
-
log.info(POLLING_STOP);
|
|
44
|
-
|
|
45
|
-
if (definitionsSyncTask.isRunning()) definitionsSyncTask.stop();
|
|
46
|
-
if (segmentsSyncTask.isRunning()) segmentsSyncTask.stop();
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
// Used by SyncManager to know if running in polling mode.
|
|
50
|
-
isRunning: definitionsSyncTask.isRunning,
|
|
51
|
-
|
|
52
|
-
syncAll() {
|
|
53
|
-
// fetch definitions and segments. There is no need to catch this promise (`DefinitionChangesUpdater` is always resolved with a boolean value)
|
|
54
|
-
return definitionsSyncTask.execute().then(() => {
|
|
55
|
-
return segmentsSyncTask.execute();
|
|
56
|
-
});
|
|
57
|
-
}
|
|
61
|
+
};
|
|
58
62
|
};
|
|
59
63
|
}
|
|
@@ -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) => {
|
|
@@ -47,7 +47,7 @@ export function segmentChangesUpdaterFactory(
|
|
|
47
47
|
false :
|
|
48
48
|
segmentChangesFetcher(since || -1, segmentName, noCache, till, _promiseDecorator).then((changes) => {
|
|
49
49
|
return Promise.all(changes.map(x => {
|
|
50
|
-
log.debug(`${LOG_PREFIX_SYNC}Processing ${segmentName} with till = ${x.till}. Added: ${x.added.length}. Removed: ${x.removed.length}`);
|
|
50
|
+
log.debug(`${LOG_PREFIX_SYNC}Processing ${segmentName} with since = ${x.since} and till = ${x.till}. Added: ${x.added.length}. Removed: ${x.removed.length}`);
|
|
51
51
|
return segments.update(segmentName, x.added, x.removed, x.till);
|
|
52
52
|
})).then((updates) => {
|
|
53
53
|
return updates.some(update => update);
|
|
@@ -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
|
|
@@ -7,16 +7,15 @@ import { SSEHandlerFactory } from './SSEHandler';
|
|
|
7
7
|
import { MySegmentsUpdateWorker } from './UpdateWorkers/MySegmentsUpdateWorker';
|
|
8
8
|
import { SegmentsUpdateWorker } from './UpdateWorkers/SegmentsUpdateWorker';
|
|
9
9
|
import { DefinitionsUpdateWorker } from './UpdateWorkers/DefinitionsUpdateWorker';
|
|
10
|
-
import { authenticateFactory, hashUserKey } from './AuthClient';
|
|
11
10
|
import { forOwn } from '../../utils/lang';
|
|
12
11
|
import { SSEClient } from './SSEClient';
|
|
13
12
|
import { checkIfServerSide, getMatching } from '../../utils/key';
|
|
14
|
-
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';
|
|
13
|
+
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';
|
|
15
14
|
import { STREAMING_FALLBACK, STREAMING_REFRESH_TOKEN, STREAMING_CONNECTING, STREAMING_DISABLED, ERROR_STREAMING_AUTH, STREAMING_DISCONNECTING, STREAMING_RECONNECT, STREAMING_PARSING_MEMBERSHIPS_UPDATE } from '../../logger/constants';
|
|
16
15
|
import { IMembershipMSUpdateData, IMembershipLSUpdateData, KeyList, UpdateStrategy } from './SSEHandler/types';
|
|
17
16
|
import { getDelay, isInBitmap, parseBitmap, parseCompressedData } from './parseUtils';
|
|
18
17
|
import { Hash64, hash64 } from '../../utils/murmur3/murmur3_64';
|
|
19
|
-
import {
|
|
18
|
+
import { IJwtCredential } from './AuthClient/types';
|
|
20
19
|
import { TOKEN_REFRESH, AUTH_REJECTION } from '../../utils/constants';
|
|
21
20
|
import { ISdkFactoryContextSync } from '../../sdkFactory/types';
|
|
22
21
|
|
|
@@ -45,7 +44,6 @@ export function pushManagerFactory(
|
|
|
45
44
|
log.warn(STREAMING_FALLBACK, [e]);
|
|
46
45
|
return;
|
|
47
46
|
}
|
|
48
|
-
const authenticate = authenticateFactory(serviceApi.fetchAuth);
|
|
49
47
|
|
|
50
48
|
// init feedback loop
|
|
51
49
|
const pushEmitter = new platform.EventEmitter() as IPushEventEmitter;
|
|
@@ -58,8 +56,6 @@ export function pushManagerFactory(
|
|
|
58
56
|
// For server-side we pass the segmentsSyncTask, used by DefinitionsUpdateWorker to fetch new segments
|
|
59
57
|
const definitionsUpdateWorker = DefinitionsUpdateWorker(log, storage, pollingManager.definitionsSyncTask, readiness.definitions, telemetryTracker);
|
|
60
58
|
|
|
61
|
-
// [Only for client-side] map of hashes to user keys, to dispatch membership update events to the corresponding MySegmentsUpdateWorker
|
|
62
|
-
const userKeyHashes: Record<string, string> = {};
|
|
63
59
|
// [Only for client-side] map of user keys to their corresponding hash64 and MySegmentsUpdateWorkers.
|
|
64
60
|
// Hash64 is used to process membership update events and dispatch actions to the corresponding MySegmentsUpdateWorker.
|
|
65
61
|
const clients: Record<string, { hash64: Hash64, worker: ReturnType<typeof MySegmentsUpdateWorker> }> = {};
|
|
@@ -81,7 +77,7 @@ export function pushManagerFactory(
|
|
|
81
77
|
let timeoutIdTokenRefresh: ReturnType<typeof setTimeout>;
|
|
82
78
|
let timeoutIdSseOpen: ReturnType<typeof setTimeout>;
|
|
83
79
|
|
|
84
|
-
function scheduleTokenRefreshAndSse(authData:
|
|
80
|
+
function scheduleTokenRefreshAndSse(authData: IJwtCredential) {
|
|
85
81
|
// clear scheduled tasks if exist
|
|
86
82
|
if (timeoutIdTokenRefresh) clearTimeout(timeoutIdTokenRefresh);
|
|
87
83
|
if (timeoutIdSseOpen) clearTimeout(timeoutIdSseOpen);
|
|
@@ -113,41 +109,37 @@ export function pushManagerFactory(
|
|
|
113
109
|
disconnected = false;
|
|
114
110
|
|
|
115
111
|
const userKeys = userKey ? Object.keys(clients) : undefined;
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (disconnected) return;
|
|
119
|
-
|
|
120
|
-
// 'pushEnabled: false' is handled as a PUSH_NON_RETRYABLE_ERROR instead of PUSH_SUBSYSTEM_DOWN, in order to
|
|
121
|
-
// close the sseClient in case the org has been bloqued while the instance was connected to streaming
|
|
122
|
-
if (!authData.pushEnabled) {
|
|
123
|
-
log.info(STREAMING_DISABLED);
|
|
124
|
-
pushEmitter.emit(PUSH_NON_RETRYABLE_ERROR);
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// [Only for client-side] don't open SSE connection if a new shared client was added, since it means that a new authentication is taking place
|
|
129
|
-
if (userKeys && userKeys.length < Object.keys(clients).length) return;
|
|
112
|
+
serviceApi.fetchAuth(userKeys).then((authData) => {
|
|
113
|
+
if (disconnected) return;
|
|
130
114
|
|
|
131
|
-
|
|
132
|
-
|
|
115
|
+
// 'pushEnabled: false' is handled as a PUSH_NON_RETRYABLE_ERROR instead of PUSH_SUBSYSTEM_DOWN, in order to
|
|
116
|
+
// close the sseClient in case the org has been bloqued while the instance was connected to streaming
|
|
117
|
+
if (!authData.pushEnabled) {
|
|
118
|
+
log.info(STREAMING_DISABLED);
|
|
119
|
+
pushEmitter.emit(PUSH_NON_RETRYABLE_ERROR);
|
|
120
|
+
return;
|
|
133
121
|
}
|
|
134
|
-
).catch(
|
|
135
|
-
function (error) {
|
|
136
|
-
if (disconnected) return;
|
|
137
122
|
|
|
138
|
-
|
|
123
|
+
// [Only for client-side] don't open SSE connection if a new shared client was added, since it means that a new authentication is taking place
|
|
124
|
+
if (userKeys && userKeys.length < Object.keys(clients).length) return;
|
|
139
125
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
126
|
+
// Schedule SSE connection and refresh token
|
|
127
|
+
scheduleTokenRefreshAndSse(authData);
|
|
128
|
+
}).catch((error) => {
|
|
129
|
+
if (disconnected) return;
|
|
146
130
|
|
|
147
|
-
|
|
148
|
-
|
|
131
|
+
log.error(ERROR_STREAMING_AUTH, [error.message]);
|
|
132
|
+
|
|
133
|
+
// Handle 4XX HTTP errors: 401 (invalid SDK Key) or 400 (using incorrect SDK Key, i.e., client-side SDK Key on server-side)
|
|
134
|
+
if (error.statusCode >= 400 && error.statusCode < 500) {
|
|
135
|
+
telemetryTracker.streamingEvent(AUTH_REJECTION);
|
|
136
|
+
pushEmitter.emit(PUSH_NON_RETRYABLE_ERROR);
|
|
137
|
+
return;
|
|
149
138
|
}
|
|
150
|
-
|
|
139
|
+
|
|
140
|
+
// Handle other HTTP and network errors as recoverable errors
|
|
141
|
+
pushEmitter.emit(PUSH_RETRYABLE_ERROR);
|
|
142
|
+
});
|
|
151
143
|
}
|
|
152
144
|
|
|
153
145
|
// close SSE connection and cancel scheduled tasks
|
|
@@ -218,8 +210,11 @@ export function pushManagerFactory(
|
|
|
218
210
|
|
|
219
211
|
/** Functions related to synchronization (Queues and Workers in the spec) */
|
|
220
212
|
|
|
213
|
+
// Some notification types are specific to the Feature flags or Configs SDKs. Their handlers are
|
|
214
|
+
// registered unconditionally, since the SDK only subscribes to the channels of its own entities.
|
|
221
215
|
pushEmitter.on(SPLIT_KILL, definitionsUpdateWorker.killDefinition);
|
|
222
216
|
pushEmitter.on(SPLIT_UPDATE, definitionsUpdateWorker.put);
|
|
217
|
+
pushEmitter.on(CONFIG_UPDATE, definitionsUpdateWorker.put);
|
|
223
218
|
pushEmitter.on(RB_SEGMENT_UPDATE, definitionsUpdateWorker.put);
|
|
224
219
|
|
|
225
220
|
function handleMySegmentsUpdate(parsedData: IMembershipMSUpdateData | IMembershipLSUpdateData) {
|
|
@@ -324,10 +319,7 @@ export function pushManagerFactory(
|
|
|
324
319
|
|
|
325
320
|
// [Only for client-side]
|
|
326
321
|
add(userKey: string, mySegmentsSyncTask: IMySegmentsSyncTask) {
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (!userKeyHashes[hash]) {
|
|
330
|
-
userKeyHashes[hash] = userKey;
|
|
322
|
+
if (!clients[userKey]) {
|
|
331
323
|
clients[userKey] = {
|
|
332
324
|
hash64: hash64(userKey),
|
|
333
325
|
worker: MySegmentsUpdateWorker(log, storage, mySegmentsSyncTask, telemetryTracker)
|
|
@@ -349,8 +341,6 @@ export function pushManagerFactory(
|
|
|
349
341
|
},
|
|
350
342
|
// [Only for client-side]
|
|
351
343
|
remove(userKey: string) {
|
|
352
|
-
const hash = hashUserKey(userKey);
|
|
353
|
-
delete userKeyHashes[hash];
|
|
354
344
|
delete clients[userKey];
|
|
355
345
|
}
|
|
356
346
|
}
|
|
@@ -16,19 +16,20 @@ export type MEMBERSHIPS_LS_UPDATE = 'MEMBERSHIPS_LS_UPDATE';
|
|
|
16
16
|
export type SEGMENT_UPDATE = 'SEGMENT_UPDATE';
|
|
17
17
|
export type SPLIT_KILL = 'SPLIT_KILL';
|
|
18
18
|
export type SPLIT_UPDATE = 'SPLIT_UPDATE';
|
|
19
|
+
export type CONFIG_UPDATE = 'CONFIG_UPDATE';
|
|
19
20
|
export type RB_SEGMENT_UPDATE = 'RB_SEGMENT_UPDATE';
|
|
20
21
|
|
|
21
22
|
// Control-type push notifications, handled by NotificationKeeper
|
|
22
23
|
export type CONTROL = 'CONTROL';
|
|
23
24
|
export type OCCUPANCY = 'OCCUPANCY';
|
|
24
25
|
|
|
25
|
-
export type IPushEvent = PUSH_SUBSYSTEM_UP | PUSH_SUBSYSTEM_DOWN | PUSH_NON_RETRYABLE_ERROR | PUSH_RETRYABLE_ERROR | MEMBERSHIPS_MS_UPDATE | MEMBERSHIPS_LS_UPDATE | SEGMENT_UPDATE | SPLIT_UPDATE | SPLIT_KILL | RB_SEGMENT_UPDATE | ControlType.STREAMING_RESET
|
|
26
|
+
export type IPushEvent = PUSH_SUBSYSTEM_UP | PUSH_SUBSYSTEM_DOWN | PUSH_NON_RETRYABLE_ERROR | PUSH_RETRYABLE_ERROR | MEMBERSHIPS_MS_UPDATE | MEMBERSHIPS_LS_UPDATE | SEGMENT_UPDATE | SPLIT_UPDATE | CONFIG_UPDATE | SPLIT_KILL | RB_SEGMENT_UPDATE | ControlType.STREAMING_RESET
|
|
26
27
|
|
|
27
28
|
type IParsedData<T extends IPushEvent> =
|
|
28
29
|
T extends MEMBERSHIPS_MS_UPDATE ? IMembershipMSUpdateData :
|
|
29
30
|
T extends MEMBERSHIPS_LS_UPDATE ? IMembershipLSUpdateData :
|
|
30
31
|
T extends SEGMENT_UPDATE ? ISegmentUpdateData :
|
|
31
|
-
T extends SPLIT_UPDATE | RB_SEGMENT_UPDATE ? ISplitUpdateData :
|
|
32
|
+
T extends SPLIT_UPDATE | CONFIG_UPDATE | RB_SEGMENT_UPDATE ? ISplitUpdateData :
|
|
32
33
|
T extends SPLIT_KILL ? ISplitKillData : INotificationData;
|
|
33
34
|
|
|
34
35
|
/**
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { groupBy, forOwn } from '../../utils/lang';
|
|
2
2
|
import SplitIO from '../../../types/splitio';
|
|
3
3
|
import { submitterFactory } from './submitter';
|
|
4
|
-
import { ImpressionsPayload } from './types';
|
|
4
|
+
import { EntityType, ImpressionsPayload } from './types';
|
|
5
5
|
import { SUBMITTERS_PUSH_FULL_QUEUE } from '../../logger/constants';
|
|
6
6
|
import { ISdkFactoryContextSync } from '../../sdkFactory/types';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* Converts `impressions` data from cache into request payload.
|
|
10
10
|
*/
|
|
11
|
-
export function fromImpressionsCollector(sendLabels: boolean, data: SplitIO.ImpressionDTO[]): ImpressionsPayload {
|
|
11
|
+
export function fromImpressionsCollector(sendLabels: boolean, data: (SplitIO.ImpressionDTO & { entityType?: EntityType })[]): ImpressionsPayload {
|
|
12
12
|
let groupedByFeature = groupBy(data, 'feature');
|
|
13
13
|
let dto: ImpressionsPayload = [];
|
|
14
14
|
|
|
@@ -25,7 +25,7 @@ export function fromImpressionsCollector(sendLabels: boolean, data: SplitIO.Impr
|
|
|
25
25
|
b: entry.bucketingKey, // Bucketing Key
|
|
26
26
|
pt: entry.pt, // Previous time
|
|
27
27
|
properties: entry.properties, // Properties
|
|
28
|
-
// @
|
|
28
|
+
// @TODO: add entityType to public SplitIO.ImpressionDTO type
|
|
29
29
|
et: entry.entityType, // Definition type
|
|
30
30
|
};
|
|
31
31
|
})
|
|
@@ -3,7 +3,7 @@ import { IMetadata } from '../../dtos/types';
|
|
|
3
3
|
import SplitIO from '../../../types/splitio';
|
|
4
4
|
import { ISyncTask } from '../types';
|
|
5
5
|
|
|
6
|
-
type EntityType = 'config' | 'flag' | 'ai-config';
|
|
6
|
+
export type EntityType = 'config' | 'flag' | 'ai-config';
|
|
7
7
|
|
|
8
8
|
type ImpressionPayload = {
|
|
9
9
|
/** Matching Key */
|
|
@@ -11,9 +11,6 @@ import { POLLING, STREAMING, SYNC_MODE_UPDATE } from '../utils/constants';
|
|
|
11
11
|
import { ISdkFactoryContextSync } from '../sdkFactory/types';
|
|
12
12
|
import { SDK_DEFINITIONS_CACHE_LOADED } from '../readiness/constants';
|
|
13
13
|
import { usesSegmentsSync } from '../storages/AbstractDefinitionsCacheSync';
|
|
14
|
-
import { splitChangesFetcherFactory } from './polling/fetchers/splitChangesFetcher';
|
|
15
|
-
import { IDefinitionChangesFetcher, ISegmentChangesFetcher } from './polling/fetchers/types';
|
|
16
|
-
import { segmentChangesFetcherFactory } from './polling/fetchers/segmentChangesFetcher';
|
|
17
14
|
|
|
18
15
|
/**
|
|
19
16
|
* Online SyncManager factory.
|
|
@@ -22,13 +19,10 @@ import { segmentChangesFetcherFactory } from './polling/fetchers/segmentChangesF
|
|
|
22
19
|
* @param pollingManagerFactory - allows to specialize the SyncManager for server-side or client-side API by passing
|
|
23
20
|
* `pollingManagerSSFactory` or `pollingManagerCSFactory` respectively.
|
|
24
21
|
* @param pushManagerFactory - optional to build a SyncManager with or without streaming support
|
|
25
|
-
* @param definitionChangesFetcherFactory - optional to replace the default split changes fetcher
|
|
26
22
|
*/
|
|
27
23
|
export function syncManagerOnlineFactory(
|
|
28
|
-
pollingManagerFactory?: (params: ISdkFactoryContextSync
|
|
24
|
+
pollingManagerFactory?: (params: ISdkFactoryContextSync) => IPollingManager,
|
|
29
25
|
pushManagerFactory?: (params: ISdkFactoryContextSync, pollingManager: IPollingManager) => IPushManager | undefined,
|
|
30
|
-
definitionFetcherFactory = splitChangesFetcherFactory,
|
|
31
|
-
segmentFetcherFactory = (params: ISdkFactoryContextSync) => segmentChangesFetcherFactory(params.serviceApi.fetchSegmentChanges)
|
|
32
26
|
): (params: ISdkFactoryContextSync) => ISyncManagerCS {
|
|
33
27
|
|
|
34
28
|
/**
|
|
@@ -39,7 +33,7 @@ export function syncManagerOnlineFactory(
|
|
|
39
33
|
const { settings, settings: { log, streamingEnabled, sync: { enabled: syncEnabled } }, telemetryTracker, storage, readiness } = params;
|
|
40
34
|
|
|
41
35
|
/** Polling Manager */
|
|
42
|
-
const pollingManager = pollingManagerFactory && pollingManagerFactory(params
|
|
36
|
+
const pollingManager = pollingManagerFactory && pollingManagerFactory(params);
|
|
43
37
|
|
|
44
38
|
/** Push Manager */
|
|
45
39
|
const pushManager = syncEnabled && streamingEnabled && pollingManager && pushManagerFactory ?
|