@splitsoftware/splitio-commons 1.1.0 → 1.2.1-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.txt +3 -0
- package/cjs/evaluator/matchers/ew.js +3 -3
- package/cjs/logger/messages/info.js +3 -3
- package/cjs/sdkClient/client.js +2 -1
- package/cjs/sdkClient/clientAttributesDecoration.js +108 -0
- package/cjs/sdkClient/clientCS.js +10 -7
- package/cjs/sdkClient/sdkClientMethodCS.js +2 -2
- package/cjs/sdkClient/sdkClientMethodCSWithTT.js +2 -2
- package/cjs/services/splitHttpClient.js +1 -1
- package/cjs/storages/inMemory/AttributesCacheInMemory.js +70 -0
- package/cjs/sync/polling/fetchers/mySegmentsFetcher.js +2 -2
- package/cjs/sync/polling/pollingManagerCS.js +2 -1
- package/cjs/sync/polling/pollingManagerSS.js +2 -1
- package/cjs/sync/polling/syncTasks/mySegmentsSyncTask.js +1 -1
- package/cjs/sync/polling/updaters/mySegmentsUpdater.js +2 -2
- package/cjs/sync/streaming/AuthClient/index.js +1 -2
- package/cjs/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.js +1 -1
- package/cjs/sync/streaming/pushManager.js +27 -23
- package/cjs/sync/submitters/submitterManager.js +2 -1
- package/cjs/sync/syncManagerOnline.js +12 -12
- package/cjs/utils/inputValidation/attribute.js +20 -0
- package/cjs/utils/inputValidation/attributes.js +13 -1
- package/cjs/utils/inputValidation/eventProperties.js +3 -1
- package/cjs/utils/lang/index.js +1 -13
- package/cjs/utils/murmur3/legacy.js +44 -0
- package/cjs/utils/promise/timeout.js +1 -1
- package/esm/evaluator/matchers/ew.js +4 -4
- package/esm/logger/messages/info.js +3 -3
- package/esm/sdkClient/client.js +2 -1
- package/esm/sdkClient/clientAttributesDecoration.js +104 -0
- package/esm/sdkClient/clientCS.js +10 -7
- package/esm/sdkClient/sdkClientMethodCS.js +2 -2
- package/esm/sdkClient/sdkClientMethodCSWithTT.js +2 -2
- package/esm/services/splitHttpClient.js +1 -1
- package/esm/storages/inMemory/AttributesCacheInMemory.js +67 -0
- package/esm/sync/polling/fetchers/mySegmentsFetcher.js +2 -2
- package/esm/sync/polling/pollingManagerCS.js +2 -1
- package/esm/sync/polling/pollingManagerSS.js +2 -1
- package/esm/sync/polling/syncTasks/mySegmentsSyncTask.js +1 -1
- package/esm/sync/polling/updaters/mySegmentsUpdater.js +2 -2
- package/esm/sync/streaming/AuthClient/index.js +1 -2
- package/esm/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.js +1 -1
- package/esm/sync/streaming/pushManager.js +27 -23
- package/esm/sync/submitters/submitterManager.js +2 -1
- package/esm/sync/syncManagerOnline.js +12 -12
- package/esm/utils/inputValidation/attribute.js +16 -0
- package/esm/utils/inputValidation/attributes.js +11 -0
- package/esm/utils/inputValidation/eventProperties.js +4 -2
- package/esm/utils/lang/index.js +0 -11
- package/esm/utils/murmur3/legacy.js +39 -0
- package/esm/utils/promise/timeout.js +1 -1
- package/package.json +4 -4
- package/src/evaluator/matchers/ew.ts +4 -4
- package/src/logger/messages/info.ts +3 -3
- package/src/sdkClient/client.ts +2 -1
- package/src/sdkClient/clientAttributesDecoration.ts +122 -0
- package/src/sdkClient/clientCS.ts +14 -7
- package/src/sdkClient/sdkClientMethodCS.ts +2 -0
- package/src/sdkClient/sdkClientMethodCSWithTT.ts +2 -0
- package/src/services/splitHttpClient.ts +1 -1
- package/src/storages/inMemory/AttributesCacheInMemory.ts +73 -0
- package/src/sync/polling/fetchers/mySegmentsFetcher.ts +2 -1
- package/src/sync/polling/fetchers/types.ts +1 -0
- package/src/sync/polling/pollingManagerCS.ts +3 -6
- package/src/sync/polling/pollingManagerSS.ts +3 -8
- package/src/sync/polling/syncTasks/mySegmentsSyncTask.ts +2 -1
- package/src/sync/polling/types.ts +0 -12
- package/src/sync/polling/updaters/mySegmentsUpdater.ts +2 -1
- package/src/sync/streaming/AuthClient/index.ts +1 -2
- package/src/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.ts +1 -1
- package/src/sync/streaming/pushManager.ts +31 -38
- package/src/sync/streaming/types.ts +5 -25
- package/src/sync/submitters/submitterManager.ts +4 -8
- package/src/sync/syncManagerOnline.ts +16 -22
- package/src/types.ts +43 -0
- package/src/utils/inputValidation/attribute.ts +21 -0
- package/src/utils/inputValidation/attributes.ts +14 -0
- package/src/utils/inputValidation/eventProperties.ts +4 -2
- package/src/utils/lang/index.ts +0 -14
- package/src/utils/murmur3/legacy.ts +48 -0
- package/src/utils/promise/timeout.ts +1 -1
- package/types/sdkClient/clientAttributesDecoration.d.ts +51 -0
- package/types/sdkClient/clientCS.d.ts +2 -1
- package/types/storages/inMemory/AttributesCacheInMemory.d.ts +43 -0
- package/types/storages/inMemory/TelemetryCacheInMemory.d.ts +51 -0
- package/types/storages/inMemory/index.d.ts +10 -0
- package/types/storages/inRedis/TelemetryCacheInRedis.d.ts +0 -0
- package/types/storages/parseSegments.d.ts +6 -0
- package/types/storages/pluggable/TelemetryCachePluggable.d.ts +2 -0
- package/types/sync/polling/fetchers/mySegmentsFetcher.d.ts +1 -1
- package/types/sync/polling/fetchers/types.d.ts +1 -1
- package/types/sync/polling/pollingManagerCS.d.ts +2 -5
- package/types/sync/polling/pollingManagerSS.d.ts +2 -5
- package/types/sync/polling/types.d.ts +0 -11
- package/types/sync/polling/updaters/mySegmentsUpdater.d.ts +1 -1
- package/types/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.d.ts +1 -1
- package/types/sync/streaming/pushManager.d.ts +3 -7
- package/types/sync/streaming/pushManagerCS.d.ts +1 -12
- package/types/sync/streaming/pushManagerSS.d.ts +1 -11
- package/types/sync/streaming/types.d.ts +3 -23
- package/types/sync/submitters/submitterManager.d.ts +2 -4
- package/types/sync/submitters/telemetrySyncTask.d.ts +17 -0
- package/types/sync/syncManagerOnline.d.ts +3 -3
- package/types/trackers/telemetryRecorder.d.ts +0 -0
- package/types/types.d.ts +40 -0
- package/types/utils/EventEmitter.d.ts +4 -0
- package/types/utils/inputValidation/attribute.d.ts +2 -0
- package/types/utils/inputValidation/attributes.d.ts +1 -0
- package/types/utils/lang/index.d.ts +0 -4
- package/types/utils/murmur3/legacy.d.ts +2 -0
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { IPushEventEmitter,
|
|
1
|
+
import { IPushEventEmitter, IPushManager } from './types';
|
|
2
2
|
import { ISSEClient } from './SSEClient/types';
|
|
3
|
-
import { IStorageSync } from '../../storages/types';
|
|
4
|
-
import { IReadinessManager } from '../../readiness/types';
|
|
5
3
|
import { ISegmentsSyncTask, IPollingManager } from '../polling/types';
|
|
6
4
|
import { objectAssign } from '../../utils/lang/objectAssign';
|
|
7
5
|
import { Backoff } from '../../utils/Backoff';
|
|
@@ -12,17 +10,15 @@ import { SplitsUpdateWorker } from './UpdateWorkers/SplitsUpdateWorker';
|
|
|
12
10
|
import { authenticateFactory, hashUserKey } from './AuthClient';
|
|
13
11
|
import { forOwn } from '../../utils/lang';
|
|
14
12
|
import { SSEClient } from './SSEClient';
|
|
15
|
-
import { IFetchAuth } from '../../services/types';
|
|
16
|
-
import { ISettings } from '../../types';
|
|
17
13
|
import { getMatching } from '../../utils/key';
|
|
18
14
|
import { MY_SEGMENTS_UPDATE, MY_SEGMENTS_UPDATE_V2, PUSH_NONRETRYABLE_ERROR, PUSH_SUBSYSTEM_DOWN, SECONDS_BEFORE_EXPIRATION, SEGMENT_UPDATE, SPLIT_KILL, SPLIT_UPDATE, PUSH_RETRYABLE_ERROR, PUSH_SUBSYSTEM_UP, ControlType } from './constants';
|
|
19
|
-
import { IPlatform } from '../../sdkFactory/types';
|
|
20
15
|
import { STREAMING_FALLBACK, STREAMING_REFRESH_TOKEN, STREAMING_CONNECTING, STREAMING_DISABLED, ERROR_STREAMING_AUTH, STREAMING_DISCONNECTING, STREAMING_RECONNECT, STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2 } from '../../logger/constants';
|
|
21
16
|
import { KeyList, UpdateStrategy } from './SSEHandler/types';
|
|
22
17
|
import { isInBitmap, parseBitmap, parseKeyList } from './mySegmentsV2utils';
|
|
23
18
|
import { ISet, _Set } from '../../utils/lang/sets';
|
|
24
19
|
import { Hash64, hash64 } from '../../utils/murmur3/murmur3_64';
|
|
25
20
|
import { IAuthTokenPushEnabled } from './AuthClient/types';
|
|
21
|
+
import { ISyncManagerFactoryParams } from '../types';
|
|
26
22
|
|
|
27
23
|
/**
|
|
28
24
|
* PushManager factory:
|
|
@@ -30,17 +26,15 @@ import { IAuthTokenPushEnabled } from './AuthClient/types';
|
|
|
30
26
|
* - for client-side, with support for multiple clients, if key is provided in settings
|
|
31
27
|
*/
|
|
32
28
|
export function pushManagerFactory(
|
|
29
|
+
params: ISyncManagerFactoryParams,
|
|
33
30
|
pollingManager: IPollingManager,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
platform: IPlatform,
|
|
38
|
-
settings: ISettings,
|
|
39
|
-
): IPushManagerCS | undefined {
|
|
31
|
+
): IPushManager | undefined {
|
|
32
|
+
|
|
33
|
+
const { settings, storage, splitApi, readiness, platform } = params;
|
|
40
34
|
|
|
41
35
|
// `userKey` is the matching key of main client in client-side SDK.
|
|
42
36
|
// It can be used to check if running on client-side or server-side SDK.
|
|
43
|
-
const userKey = settings.core.key ? getMatching(settings.core.key) : undefined;
|
|
37
|
+
const userKey = settings.core.key ? getMatching(settings.core.key) : undefined;
|
|
44
38
|
const log = settings.log;
|
|
45
39
|
|
|
46
40
|
let sseClient: ISSEClient;
|
|
@@ -51,7 +45,7 @@ export function pushManagerFactory(
|
|
|
51
45
|
log.warn(STREAMING_FALLBACK, [e]);
|
|
52
46
|
return;
|
|
53
47
|
}
|
|
54
|
-
const authenticate = authenticateFactory(fetchAuth);
|
|
48
|
+
const authenticate = authenticateFactory(splitApi.fetchAuth);
|
|
55
49
|
|
|
56
50
|
// init feedback loop
|
|
57
51
|
const pushEmitter = new platform.EventEmitter() as IPushEventEmitter;
|
|
@@ -59,7 +53,8 @@ export function pushManagerFactory(
|
|
|
59
53
|
sseClient.setEventHandler(sseHandler);
|
|
60
54
|
|
|
61
55
|
// init workers
|
|
62
|
-
|
|
56
|
+
// MySegmentsUpdateWorker (client-side) are initiated in `add` method
|
|
57
|
+
const segmentsUpdateWorker = userKey ? undefined : new SegmentsUpdateWorker(pollingManager.segmentsSyncTask, storage.segments);
|
|
63
58
|
// For server-side we pass the segmentsSyncTask, used by SplitsUpdateWorker to fetch new segments
|
|
64
59
|
const splitsUpdateWorker = new SplitsUpdateWorker(storage.splits, pollingManager.splitsSyncTask, readiness.splits, userKey ? undefined : pollingManager.segmentsSyncTask);
|
|
65
60
|
|
|
@@ -68,11 +63,6 @@ export function pushManagerFactory(
|
|
|
68
63
|
// [Only for client-side] map of user keys to their corresponding hash64 and MySegmentsUpdateWorkers.
|
|
69
64
|
// Hash64 is used to process MY_SEGMENTS_UPDATE_V2 events and dispatch actions to the corresponding MySegmentsUpdateWorker.
|
|
70
65
|
const clients: Record<string, { hash64: Hash64, worker: MySegmentsUpdateWorker }> = {};
|
|
71
|
-
if (userKey) {
|
|
72
|
-
const hash = hashUserKey(userKey);
|
|
73
|
-
userKeyHashes[hash] = userKey;
|
|
74
|
-
clients[userKey] = { hash64: hash64(userKey), worker: segmentsUpdateWorker as MySegmentsUpdateWorker };
|
|
75
|
-
}
|
|
76
66
|
|
|
77
67
|
// [Only for client-side] variable to flag that a new client was added. It is needed to reconnect streaming.
|
|
78
68
|
let connectForNewClient = false;
|
|
@@ -176,7 +166,7 @@ export function pushManagerFactory(
|
|
|
176
166
|
function stopWorkers() {
|
|
177
167
|
splitsUpdateWorker.backoff.reset();
|
|
178
168
|
if (userKey) forOwn(clients, ({ worker }) => worker.backoff.reset());
|
|
179
|
-
else segmentsUpdateWorker.backoff.reset();
|
|
169
|
+
else (segmentsUpdateWorker as SegmentsUpdateWorker).backoff.reset();
|
|
180
170
|
}
|
|
181
171
|
|
|
182
172
|
pushEmitter.on(PUSH_SUBSYSTEM_DOWN, stopWorkers);
|
|
@@ -306,37 +296,40 @@ export function pushManagerFactory(
|
|
|
306
296
|
// Expose Event Emitter functionality and Event constants
|
|
307
297
|
Object.create(pushEmitter),
|
|
308
298
|
{
|
|
309
|
-
//
|
|
310
|
-
stop
|
|
311
|
-
|
|
299
|
+
// Stop/pause push mode
|
|
300
|
+
stop() {
|
|
301
|
+
disconnectPush(); // `handleNonRetryableError` cannot be used as `stop`, because it emits PUSH_SUBSYSTEM_DOWN event, which starts polling.
|
|
302
|
+
if (userKey) this.remove(userKey); // Necessary to properly resume streaming in client-side (e.g., RN SDK transition to foreground).
|
|
303
|
+
},
|
|
304
|
+
// Start/resume push mode
|
|
312
305
|
start() {
|
|
313
306
|
// Guard condition to avoid calling `connectPush` again if the `start` method is called multiple times or if push has been disabled.
|
|
314
307
|
if (disabled || disconnected === false) return;
|
|
315
308
|
disconnected = false;
|
|
316
|
-
|
|
317
|
-
|
|
309
|
+
|
|
310
|
+
if (userKey) this.add(userKey, pollingManager.segmentsSyncTask); // client-side
|
|
311
|
+
else setTimeout(connectPush); // server-side runs in next cycle as in client-side, for consistency with client-side
|
|
318
312
|
},
|
|
319
313
|
|
|
320
314
|
// [Only for client-side]
|
|
321
315
|
add(userKey: string, mySegmentsSyncTask: ISegmentsSyncTask) {
|
|
322
|
-
clients[userKey] = { hash64: hash64(userKey), worker: new MySegmentsUpdateWorker(mySegmentsSyncTask) };
|
|
323
|
-
|
|
324
316
|
const hash = hashUserKey(userKey);
|
|
325
317
|
|
|
326
318
|
if (!userKeyHashes[hash]) {
|
|
327
319
|
userKeyHashes[hash] = userKey;
|
|
320
|
+
clients[userKey] = { hash64: hash64(userKey), worker: new MySegmentsUpdateWorker(mySegmentsSyncTask) };
|
|
328
321
|
connectForNewClient = true; // we must reconnect on start, to listen the channel for the new user key
|
|
329
|
-
}
|
|
330
322
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
323
|
+
// Reconnects in case of a new client.
|
|
324
|
+
// Run in next event-loop cycle to save authentication calls
|
|
325
|
+
// in case multiple clients are created in the current cycle.
|
|
326
|
+
setTimeout(function checkForReconnect() {
|
|
327
|
+
if (connectForNewClient) {
|
|
328
|
+
connectForNewClient = false;
|
|
329
|
+
connectPush();
|
|
330
|
+
}
|
|
331
|
+
}, 0);
|
|
332
|
+
}
|
|
340
333
|
},
|
|
341
334
|
// [Only for client-side]
|
|
342
335
|
remove(userKey: string) {
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { IMySegmentsUpdateData, IMySegmentsUpdateV2Data, ISegmentUpdateData, ISplitUpdateData, ISplitKillData } from './SSEHandler/types';
|
|
2
2
|
import { ITask } from '../types';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { IFetchAuth } from '../../services/types';
|
|
6
|
-
import { IStorageSync } from '../../storages/types';
|
|
7
|
-
import { IEventEmitter, ISettings } from '../../types';
|
|
8
|
-
import { IPlatform } from '../../sdkFactory/types';
|
|
3
|
+
import { ISegmentsSyncTask } from '../polling/types';
|
|
4
|
+
import { IEventEmitter } from '../../types';
|
|
9
5
|
import { ControlType } from './constants';
|
|
10
6
|
|
|
11
7
|
// Internal SDK events, subscribed by SyncManager and PushManager
|
|
@@ -45,26 +41,10 @@ export interface IPushEventEmitter extends IEventEmitter {
|
|
|
45
41
|
}
|
|
46
42
|
|
|
47
43
|
/**
|
|
48
|
-
* PushManager
|
|
44
|
+
* PushManager
|
|
49
45
|
*/
|
|
50
|
-
export interface IPushManager extends ITask, IPushEventEmitter {
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* PushManager for client-side with support for multiple clients
|
|
54
|
-
*/
|
|
55
|
-
export interface IPushManagerCS extends IPushManager {
|
|
46
|
+
export interface IPushManager extends ITask, IPushEventEmitter {
|
|
47
|
+
// Methods used in client-side, to support multiple clients
|
|
56
48
|
add(userKey: string, mySegmentsSyncTask: ISegmentsSyncTask): void,
|
|
57
49
|
remove(userKey: string): void
|
|
58
50
|
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Signature of push manager factory/constructor
|
|
62
|
-
*/
|
|
63
|
-
export type IPushManagerFactoryParams = [
|
|
64
|
-
pollingManager: IPollingManager,
|
|
65
|
-
storage: IStorageSync,
|
|
66
|
-
readiness: IReadinessManager,
|
|
67
|
-
fetchAuth: IFetchAuth,
|
|
68
|
-
platform: IPlatform,
|
|
69
|
-
settings: ISettings
|
|
70
|
-
]
|
|
@@ -2,15 +2,11 @@ import { syncTaskComposite } from '../syncTaskComposite';
|
|
|
2
2
|
import { eventsSyncTaskFactory } from './eventsSyncTask';
|
|
3
3
|
import { impressionsSyncTaskFactory } from './impressionsSyncTask';
|
|
4
4
|
import { impressionCountsSyncTaskFactory } from './impressionCountsSyncTask';
|
|
5
|
-
import {
|
|
6
|
-
import { IStorageSync } from '../../storages/types';
|
|
7
|
-
import { ISettings } from '../../types';
|
|
5
|
+
import { ISyncManagerFactoryParams } from '../types';
|
|
8
6
|
|
|
9
|
-
export function submitterManagerFactory(
|
|
10
|
-
|
|
11
|
-
storage
|
|
12
|
-
splitApi: ISplitApi,
|
|
13
|
-
) {
|
|
7
|
+
export function submitterManagerFactory(params: ISyncManagerFactoryParams) {
|
|
8
|
+
|
|
9
|
+
const { settings, storage, splitApi } = params;
|
|
14
10
|
const log = settings.log;
|
|
15
11
|
const submitters = [
|
|
16
12
|
impressionsSyncTaskFactory(log, splitApi.postTestImpressionsBulk, storage.impressions, settings.scheduler.impressionsRefreshRate, settings.core.labelsEnabled),
|
|
@@ -2,8 +2,8 @@ import { ISyncManagerCS, ISyncManagerFactoryParams } from './types';
|
|
|
2
2
|
import { submitterManagerFactory } from './submitters/submitterManager';
|
|
3
3
|
import { IReadinessManager } from '../readiness/types';
|
|
4
4
|
import { IStorageSync } from '../storages/types';
|
|
5
|
-
import {
|
|
6
|
-
import { IPollingManager, IPollingManagerCS
|
|
5
|
+
import { IPushManager } from './streaming/types';
|
|
6
|
+
import { IPollingManager, IPollingManagerCS } from './polling/types';
|
|
7
7
|
import { PUSH_SUBSYSTEM_UP, PUSH_SUBSYSTEM_DOWN } from './streaming/constants';
|
|
8
8
|
import { SYNC_START_POLLING, SYNC_CONTINUE_POLLING, SYNC_STOP_POLLING } from '../logger/constants';
|
|
9
9
|
|
|
@@ -16,44 +16,38 @@ import { SYNC_START_POLLING, SYNC_CONTINUE_POLLING, SYNC_STOP_POLLING } from '..
|
|
|
16
16
|
* @param pushManagerFactory optional to build a SyncManager with or without streaming support
|
|
17
17
|
*/
|
|
18
18
|
export function syncManagerOnlineFactory(
|
|
19
|
-
pollingManagerFactory?: (
|
|
20
|
-
pushManagerFactory?: (
|
|
19
|
+
pollingManagerFactory?: (params: ISyncManagerFactoryParams) => IPollingManager,
|
|
20
|
+
pushManagerFactory?: (params: ISyncManagerFactoryParams, pollingManager: IPollingManager) => IPushManager | undefined,
|
|
21
21
|
): (params: ISyncManagerFactoryParams) => ISyncManagerCS {
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* SyncManager factory for modular SDK
|
|
25
25
|
*/
|
|
26
|
-
return function ({
|
|
27
|
-
settings,
|
|
28
|
-
platform,
|
|
29
|
-
splitApi,
|
|
30
|
-
storage,
|
|
31
|
-
readiness
|
|
32
|
-
}: ISyncManagerFactoryParams): ISyncManagerCS {
|
|
26
|
+
return function (params: ISyncManagerFactoryParams): ISyncManagerCS {
|
|
33
27
|
|
|
34
|
-
const log = settings
|
|
28
|
+
const { log, streamingEnabled } = params.settings;
|
|
35
29
|
|
|
36
30
|
/** Polling Manager */
|
|
37
|
-
const pollingManager = pollingManagerFactory && pollingManagerFactory(
|
|
31
|
+
const pollingManager = pollingManagerFactory && pollingManagerFactory(params);
|
|
38
32
|
|
|
39
33
|
/** Push Manager */
|
|
40
|
-
const pushManager =
|
|
41
|
-
pushManagerFactory(
|
|
34
|
+
const pushManager = streamingEnabled && pollingManager && pushManagerFactory ?
|
|
35
|
+
pushManagerFactory(params, pollingManager) :
|
|
42
36
|
undefined;
|
|
43
37
|
|
|
44
38
|
/** Submitter Manager */
|
|
45
39
|
// It is not inyected as push and polling managers, because at the moment it is required
|
|
46
|
-
const submitter = submitterManagerFactory(
|
|
40
|
+
const submitter = submitterManagerFactory(params);
|
|
47
41
|
|
|
48
42
|
|
|
49
43
|
/** Sync Manager logic */
|
|
50
44
|
|
|
51
45
|
function startPolling() {
|
|
52
|
-
if (
|
|
46
|
+
if (pollingManager!.isRunning()) {
|
|
47
|
+
log.info(SYNC_CONTINUE_POLLING);
|
|
48
|
+
} else {
|
|
53
49
|
log.info(SYNC_START_POLLING);
|
|
54
50
|
pollingManager!.start();
|
|
55
|
-
} else {
|
|
56
|
-
log.info(SYNC_CONTINUE_POLLING);
|
|
57
51
|
}
|
|
58
52
|
}
|
|
59
53
|
|
|
@@ -96,7 +90,7 @@ export function syncManagerOnlineFactory(
|
|
|
96
90
|
}
|
|
97
91
|
|
|
98
92
|
// start periodic data recording (events, impressions, telemetry).
|
|
99
|
-
submitter
|
|
93
|
+
if (submitter) submitter.start();
|
|
100
94
|
running = true;
|
|
101
95
|
},
|
|
102
96
|
|
|
@@ -141,7 +135,7 @@ export function syncManagerOnlineFactory(
|
|
|
141
135
|
// of segments since `syncAll` was already executed when starting the main client
|
|
142
136
|
mySegmentsSyncTask.execute();
|
|
143
137
|
}
|
|
144
|
-
|
|
138
|
+
pushManager.add(matchingKey, mySegmentsSyncTask);
|
|
145
139
|
} else {
|
|
146
140
|
if (storage.splits.usesSegments()) mySegmentsSyncTask.start();
|
|
147
141
|
}
|
|
@@ -151,7 +145,7 @@ export function syncManagerOnlineFactory(
|
|
|
151
145
|
const mySegmentsSyncTask = (pollingManager as IPollingManagerCS).get(matchingKey);
|
|
152
146
|
if (mySegmentsSyncTask) {
|
|
153
147
|
// stop syncing
|
|
154
|
-
if (pushManager)
|
|
148
|
+
if (pushManager) pushManager.remove(matchingKey);
|
|
155
149
|
if (mySegmentsSyncTask.isRunning()) mySegmentsSyncTask.stop();
|
|
156
150
|
|
|
157
151
|
(pollingManager as IPollingManagerCS).remove(matchingKey);
|
package/src/types.ts
CHANGED
|
@@ -381,6 +381,10 @@ interface IBasicClient extends IStatusInterface {
|
|
|
381
381
|
* @returns {Promise<void>}
|
|
382
382
|
*/
|
|
383
383
|
destroy(): Promise<void>
|
|
384
|
+
|
|
385
|
+
// Whether the client implements the client-side API, i.e, with bound key, (true), or the server-side API (false).
|
|
386
|
+
// Exposed for internal purposes only. Not considered part of the public API, and might be renamed eventually.
|
|
387
|
+
isBrowserClient: boolean
|
|
384
388
|
}
|
|
385
389
|
/**
|
|
386
390
|
* Common definitions between SDK instances for different environments interface.
|
|
@@ -1108,6 +1112,45 @@ export namespace SplitIO {
|
|
|
1108
1112
|
* @returns {boolean} Whether the event was added to the queue succesfully or not.
|
|
1109
1113
|
*/
|
|
1110
1114
|
track(...args: [trafficType: string, eventType: string, value?: number, properties?: Properties] | [eventType: string, value?: number, properties?: Properties]): boolean,
|
|
1115
|
+
/**
|
|
1116
|
+
* Add an attribute to client's in memory attributes storage
|
|
1117
|
+
* @function setAttribute
|
|
1118
|
+
* @param {string} attributeName Attrinute name
|
|
1119
|
+
* @param {string, number, boolean, list} attributeValue Attribute value
|
|
1120
|
+
* @returns {boolean} true if the attribute was stored and false otherways
|
|
1121
|
+
*/
|
|
1122
|
+
setAttribute(attributeName: string, attributeValue: Object): boolean,
|
|
1123
|
+
/**
|
|
1124
|
+
* Returns the attribute with the given key
|
|
1125
|
+
* @function getAttribute
|
|
1126
|
+
* @param {string} attributeName Attribute name
|
|
1127
|
+
* @returns {Object} Attribute with the given key
|
|
1128
|
+
*/
|
|
1129
|
+
getAttribute(attributeName: string): Object,
|
|
1130
|
+
/**
|
|
1131
|
+
* Add to client's in memory attributes storage the attributes in 'attributes'
|
|
1132
|
+
* @function setAttributes
|
|
1133
|
+
* @param {Object} attributes Object with attributes to store
|
|
1134
|
+
* @returns true if attributes were stored an false otherways
|
|
1135
|
+
*/
|
|
1136
|
+
setAttributes(attributes: Record<string, Object>): boolean,
|
|
1137
|
+
/**
|
|
1138
|
+
* Return all the attributes stored in client's in memory attributes storage
|
|
1139
|
+
* @function getAttributes
|
|
1140
|
+
* @returns {Object} returns all the stored attributes
|
|
1141
|
+
*/
|
|
1142
|
+
getAttributes(): Record<string, Object>,
|
|
1143
|
+
/**
|
|
1144
|
+
* Removes from client's in memory attributes storage the attribute with the given key
|
|
1145
|
+
* @function removeAttribute
|
|
1146
|
+
* @param {string} attributeName
|
|
1147
|
+
* @returns {boolean} true if attribute was removed and false otherways
|
|
1148
|
+
*/
|
|
1149
|
+
removeAttribute(attributeName: string): boolean,
|
|
1150
|
+
/**
|
|
1151
|
+
* Remove all the stored attributes in the client's in memory attribute storage
|
|
1152
|
+
*/
|
|
1153
|
+
clearAttributes(): boolean
|
|
1111
1154
|
}
|
|
1112
1155
|
/**
|
|
1113
1156
|
* Representation of a manager instance with synchronous storage of the SDK.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { isString, isFiniteNumber, isBoolean } from '../../utils/lang';
|
|
2
|
+
import { ILogger } from '../../logger/types';
|
|
3
|
+
|
|
4
|
+
export function validateAttribute(log: ILogger, attributeKey: string, attributeValue: Object, method: string): boolean {
|
|
5
|
+
if (!isString(attributeKey) || attributeKey.length === 0) {
|
|
6
|
+
log.warn(`${method}: you passed an invalid attribute name, attribute name must be a non-empty string.`);
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const isStringVal = isString(attributeValue);
|
|
11
|
+
const isFiniteVal = isFiniteNumber(attributeValue);
|
|
12
|
+
const isBoolVal = isBoolean(attributeValue);
|
|
13
|
+
const isArrayVal = Array.isArray(attributeValue);
|
|
14
|
+
|
|
15
|
+
if (!(isStringVal || isFiniteVal || isBoolVal || isArrayVal)) { // If it's not of valid type.
|
|
16
|
+
log.warn(`${method}: you passed an invalid attribute value for ${attributeKey}. Acceptable types are: string, number, boolean and array of strings.`);
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isObject } from '../lang';
|
|
2
2
|
import { SplitIO } from '../../types';
|
|
3
3
|
import { ILogger } from '../../logger/types';
|
|
4
|
+
import { validateAttribute } from './attribute';
|
|
4
5
|
import { ERROR_NOT_PLAIN_OBJECT } from '../../logger/constants';
|
|
5
6
|
|
|
6
7
|
export function validateAttributes(log: ILogger, maybeAttrs: any, method: string): SplitIO.Attributes | undefined | false {
|
|
@@ -11,3 +12,16 @@ export function validateAttributes(log: ILogger, maybeAttrs: any, method: string
|
|
|
11
12
|
log.error(ERROR_NOT_PLAIN_OBJECT, [method, 'attributes']);
|
|
12
13
|
return false;
|
|
13
14
|
}
|
|
15
|
+
|
|
16
|
+
export function validateAttributesDeep(log: ILogger, maybeAttributes: Record<string, Object>, method: string): boolean {
|
|
17
|
+
if (!validateAttributes(log, maybeAttributes, method)) return false;
|
|
18
|
+
|
|
19
|
+
let result = true;
|
|
20
|
+
Object.keys(maybeAttributes).forEach(attributeKey => {
|
|
21
|
+
if (!validateAttribute(log, attributeKey, maybeAttributes[attributeKey], method))
|
|
22
|
+
result = false;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return result;
|
|
26
|
+
|
|
27
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { isObject,
|
|
1
|
+
import { isObject, isString, isFiniteNumber, isBoolean } from '../lang';
|
|
2
|
+
import { objectAssign } from '../lang/objectAssign';
|
|
2
3
|
import { SplitIO } from '../../types';
|
|
3
4
|
import { ILogger } from '../../logger/types';
|
|
4
5
|
import { ERROR_NOT_PLAIN_OBJECT, ERROR_SIZE_EXCEEDED, WARN_SETTING_NULL, WARN_TRIMMING_PROPERTIES } from '../../logger/constants';
|
|
@@ -22,7 +23,8 @@ export function validateEventProperties(log: ILogger, maybeProperties: any, meth
|
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
const keys = Object.keys(maybeProperties);
|
|
25
|
-
|
|
26
|
+
// Shallow clone
|
|
27
|
+
const clone = objectAssign({}, maybeProperties);
|
|
26
28
|
// To avoid calculating the size twice we'll return it from here.
|
|
27
29
|
const output = {
|
|
28
30
|
properties: clone,
|
package/src/utils/lang/index.ts
CHANGED
|
@@ -194,20 +194,6 @@ export function merge(target: { [key: string]: any }, source: { [key: string]: a
|
|
|
194
194
|
return res;
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
/**
|
|
198
|
-
* Shallow clone an object
|
|
199
|
-
*/
|
|
200
|
-
export function shallowClone(obj: any): any {
|
|
201
|
-
const keys = Object.keys(obj);
|
|
202
|
-
const output: Record<string, any> = {};
|
|
203
|
-
|
|
204
|
-
for (let i = 0; i < keys.length; i++) {
|
|
205
|
-
output[keys[i]] = obj[keys[i]];
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return output;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
197
|
/**
|
|
212
198
|
* Checks if the target string starts with the sub string.
|
|
213
199
|
*/
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Deprecated hashing function, used for split bucketing. Replaced by murmur3
|
|
2
|
+
|
|
3
|
+
//
|
|
4
|
+
// JAVA reference implementation for the hashing function.
|
|
5
|
+
//
|
|
6
|
+
// int h = 0;
|
|
7
|
+
// for (int i = 0; i < key.length(); i++) {
|
|
8
|
+
// h = 31 * h + key.charAt(i);
|
|
9
|
+
// }
|
|
10
|
+
// return h ^ seed; // XOR the hash and seed
|
|
11
|
+
//
|
|
12
|
+
|
|
13
|
+
function ToInteger(x: number) {
|
|
14
|
+
x = Number(x);
|
|
15
|
+
return x < 0 ? Math.ceil(x) : Math.floor(x);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function modulo(a: number, b: number) {
|
|
19
|
+
return a - Math.floor(a / b) * b;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function ToUint32(x: number) {
|
|
23
|
+
return modulo(ToInteger(x), Math.pow(2, 32));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function ToInt32(x: number) {
|
|
27
|
+
let uint32 = ToUint32(x);
|
|
28
|
+
|
|
29
|
+
if (uint32 >= Math.pow(2, 31)) {
|
|
30
|
+
return uint32 - Math.pow(2, 32);
|
|
31
|
+
} else {
|
|
32
|
+
return uint32;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function hash(str: string, seed: number): number {
|
|
37
|
+
let h = 0;
|
|
38
|
+
|
|
39
|
+
for (let i = 0; i < str.length; i++) {
|
|
40
|
+
h = ToInt32(ToInt32(31 * h) + str.charCodeAt(i));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return ToInt32(h ^ seed);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function bucket(str: string, seed: number): number {
|
|
47
|
+
return Math.abs(hash(str, seed) % 100) + 1;
|
|
48
|
+
}
|
|
@@ -3,7 +3,7 @@ export function timeout<T>(ms: number, promise: Promise<T>): Promise<T> {
|
|
|
3
3
|
|
|
4
4
|
return new Promise((resolve, reject) => {
|
|
5
5
|
const tid = setTimeout(() => {
|
|
6
|
-
reject(new Error(`Operation timed out because it exceeded the configured time limit of ${ms}ms.`));
|
|
6
|
+
reject(new Error(`Operation timed out because it exceeded the configured time limit of ${ms} ms.`));
|
|
7
7
|
}, ms);
|
|
8
8
|
|
|
9
9
|
promise.then((res) => {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { SplitIO } from '../types';
|
|
2
|
+
import { ILogger } from '../logger/types';
|
|
3
|
+
/**
|
|
4
|
+
* Add in memory attributes storage methods and combine them with any attribute received from the getTreatment/s call
|
|
5
|
+
*/
|
|
6
|
+
export declare function clientAttributesDecoration<TClient extends SplitIO.IClient | SplitIO.IAsyncClient>(log: ILogger, client: TClient): TClient & {
|
|
7
|
+
getTreatment: (maybeKey: SplitIO.SplitKey, maybeSplit: string, maybeAttributes?: SplitIO.Attributes | undefined) => string | SplitIO.AsyncTreatment;
|
|
8
|
+
getTreatmentWithConfig: (maybeKey: SplitIO.SplitKey, maybeSplit: string, maybeAttributes?: SplitIO.Attributes | undefined) => SplitIO.TreatmentWithConfig | SplitIO.AsyncTreatmentWithConfig;
|
|
9
|
+
getTreatments: (maybeKey: SplitIO.SplitKey, maybeSplits: string[], maybeAttributes?: SplitIO.Attributes | undefined) => SplitIO.Treatments | SplitIO.AsyncTreatments;
|
|
10
|
+
getTreatmentsWithConfig: (maybeKey: SplitIO.SplitKey, maybeSplits: string[], maybeAttributes?: SplitIO.Attributes | undefined) => SplitIO.TreatmentsWithConfig | SplitIO.AsyncTreatmentsWithConfig;
|
|
11
|
+
track: (maybeKey: SplitIO.SplitKey, maybeTT: string, maybeEvent: string, maybeEventValue?: number | undefined, maybeProperties?: SplitIO.Properties | undefined) => import("../dtos/types").MaybeThenable<boolean>;
|
|
12
|
+
/**
|
|
13
|
+
* Add an attribute to client's in memory attributes storage
|
|
14
|
+
*
|
|
15
|
+
* @param {string} attributeName Attrinute name
|
|
16
|
+
* @param {string, number, boolean, list} attributeValue Attribute value
|
|
17
|
+
* @returns {boolean} true if the attribute was stored and false otherways
|
|
18
|
+
*/
|
|
19
|
+
setAttribute(attributeName: string, attributeValue: Object): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Returns the attribute with the given key
|
|
22
|
+
*
|
|
23
|
+
* @param {string} attributeName Attribute name
|
|
24
|
+
* @returns {Object} Attribute with the given key
|
|
25
|
+
*/
|
|
26
|
+
getAttribute(attributeName: string): Object;
|
|
27
|
+
/**
|
|
28
|
+
* Add to client's in memory attributes storage the attributes in 'attributes'
|
|
29
|
+
*
|
|
30
|
+
* @param {Object} attributes Object with attributes to store
|
|
31
|
+
* @returns true if attributes were stored an false otherways
|
|
32
|
+
*/
|
|
33
|
+
setAttributes(attributes: Record<string, Object>): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Return all the attributes stored in client's in memory attributes storage
|
|
36
|
+
*
|
|
37
|
+
* @returns {Object} returns all the stored attributes
|
|
38
|
+
*/
|
|
39
|
+
getAttributes(): Record<string, Object>;
|
|
40
|
+
/**
|
|
41
|
+
* Removes from client's in memory attributes storage the attribute with the given key
|
|
42
|
+
*
|
|
43
|
+
* @param {string} attributeName
|
|
44
|
+
* @returns {boolean} true if attribute was removed and false otherways
|
|
45
|
+
*/
|
|
46
|
+
removeAttribute(attributeName: string): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Remove all the stored attributes in the client's in memory attribute storage
|
|
49
|
+
*/
|
|
50
|
+
clearAttributes(): boolean;
|
|
51
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ILogger } from '../logger/types';
|
|
1
2
|
import { SplitIO } from '../types';
|
|
2
3
|
/**
|
|
3
4
|
* Decorator that binds a key and (optionally) a traffic type to client methods
|
|
@@ -6,4 +7,4 @@ import { SplitIO } from '../types';
|
|
|
6
7
|
* @param key validated split key
|
|
7
8
|
* @param trafficType validated traffic type
|
|
8
9
|
*/
|
|
9
|
-
export declare function clientCSDecorator(client: SplitIO.IClient, key: SplitIO.SplitKey, trafficType?: string): SplitIO.ICsClient;
|
|
10
|
+
export declare function clientCSDecorator(log: ILogger, client: SplitIO.IClient, key: SplitIO.SplitKey, trafficType?: string): SplitIO.ICsClient;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export declare class AttributesCacheInMemory {
|
|
2
|
+
private attributesCache;
|
|
3
|
+
/**
|
|
4
|
+
* Create or update the value for the given attribute
|
|
5
|
+
*
|
|
6
|
+
* @param {string} attributeName attribute name
|
|
7
|
+
* @param {Object} attributeValue attribute value
|
|
8
|
+
* @returns {boolean} the attribute was stored
|
|
9
|
+
*/
|
|
10
|
+
setAttribute(attributeName: string, attributeValue: Object): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Retrieves the value of a given attribute
|
|
13
|
+
*
|
|
14
|
+
* @param {string} attributeName attribute name
|
|
15
|
+
* @returns {Object?} stored attribute value
|
|
16
|
+
*/
|
|
17
|
+
getAttribute(attributeName: string): Object;
|
|
18
|
+
/**
|
|
19
|
+
* Create or update all the given attributes
|
|
20
|
+
*
|
|
21
|
+
* @param {[string, Object]} attributes attributes to create or update
|
|
22
|
+
* @returns {boolean} attributes were stored
|
|
23
|
+
*/
|
|
24
|
+
setAttributes(attributes: Record<string, Object>): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Retrieve the full attributes map
|
|
27
|
+
*
|
|
28
|
+
* @returns {Map<string, Object>} stored attributes
|
|
29
|
+
*/
|
|
30
|
+
getAll(): Record<string, Object>;
|
|
31
|
+
/**
|
|
32
|
+
* Removes a given attribute from the map
|
|
33
|
+
*
|
|
34
|
+
* @param {string} attributeName attribute to remove
|
|
35
|
+
* @returns {boolean} attribute removed
|
|
36
|
+
*/
|
|
37
|
+
removeAttribute(attributeName: string): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Clears all attributes stored in the SDK
|
|
40
|
+
*
|
|
41
|
+
*/
|
|
42
|
+
clear(): boolean;
|
|
43
|
+
}
|