From f4fbd1a105ecfb5f4b3163c5612f0928dab05dc8 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 10 Feb 2022 17:16:16 -0300 Subject: [PATCH 1/5] update push manager and add unit tests --- package-lock.json | 2 +- package.json | 2 +- src/logger/messages/info.ts | 6 +- src/sync/__tests__/syncTask.mock.ts | 9 + src/sync/streaming/AuthClient/index.ts | 3 +- .../streaming/__tests__/pushManager.spec.ts | 157 ++++++++++++++++-- src/sync/streaming/pushManager.ts | 49 +++--- src/sync/syncManagerOnline.ts | 16 +- .../__tests__/settings.mocks.ts | 11 +- 9 files changed, 204 insertions(+), 51 deletions(-) create mode 100644 src/sync/__tests__/syncTask.mock.ts diff --git a/package-lock.json b/package-lock.json index 00e802b2..9456d98a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "1.2.0", + "version": "1.2.1-rc.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 9cce3f8a..012f2ef0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "1.2.0", + "version": "1.2.1-rc.0", "description": "Split Javascript SDK common components", "main": "cjs/index.js", "module": "esm/index.js", diff --git a/src/logger/messages/info.ts b/src/logger/messages/info.ts index 42838345..298711bd 100644 --- a/src/logger/messages/info.ts +++ b/src/logger/messages/info.ts @@ -23,10 +23,10 @@ export const codesInfo: [number, string][] = codesWarn.concat([ [c.SUBMITTERS_PUSH_FULL_EVENTS_QUEUE, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Flushing full events queue and reseting timer.'], [c.SUBMITTERS_PUSH, c.LOG_PREFIX_SYNC_SUBMITTERS + 'Pushing %s %s.'], [c.STREAMING_REFRESH_TOKEN, c.LOG_PREFIX_SYNC_STREAMING + 'Refreshing streaming token in %s seconds, and connecting streaming in %s seconds.'], - [c.STREAMING_RECONNECT, c.LOG_PREFIX_SYNC_STREAMING + 'Attempting to reconnect in %s seconds.'], - [c.STREAMING_CONNECTING, c.LOG_PREFIX_SYNC_STREAMING + 'Connecting to streaming.'], + [c.STREAMING_RECONNECT, c.LOG_PREFIX_SYNC_STREAMING + 'Attempting to reconnect streaming in %s seconds.'], + [c.STREAMING_CONNECTING, c.LOG_PREFIX_SYNC_STREAMING + 'Connecting streaming.'], [c.STREAMING_DISABLED, c.LOG_PREFIX_SYNC_STREAMING + 'Streaming is disabled for given Api key. Switching to polling mode.'], - [c.STREAMING_DISCONNECTING, c.LOG_PREFIX_SYNC_STREAMING + 'Disconnecting from streaming.'], + [c.STREAMING_DISCONNECTING, c.LOG_PREFIX_SYNC_STREAMING + 'Disconnecting streaming.'], [c.SYNC_START_POLLING, c.LOG_PREFIX_SYNC_MANAGER + 'Streaming not available. Starting polling.'], [c.SYNC_CONTINUE_POLLING, c.LOG_PREFIX_SYNC_MANAGER + 'Streaming couldn\'t connect. Continue polling.'], [c.SYNC_STOP_POLLING, c.LOG_PREFIX_SYNC_MANAGER + 'Streaming (re)connected. Syncing and stopping polling.'], diff --git a/src/sync/__tests__/syncTask.mock.ts b/src/sync/__tests__/syncTask.mock.ts new file mode 100644 index 00000000..10ef439e --- /dev/null +++ b/src/sync/__tests__/syncTask.mock.ts @@ -0,0 +1,9 @@ +export function syncTaskFactory() { + return { + execute: jest.fn(), + isExecuting: jest.fn(), + start: jest.fn(), + stop: jest.fn(), + isRunning: jest.fn(), + }; +} diff --git a/src/sync/streaming/AuthClient/index.ts b/src/sync/streaming/AuthClient/index.ts index 96ae9ca8..b8d81c55 100644 --- a/src/sync/streaming/AuthClient/index.ts +++ b/src/sync/streaming/AuthClient/index.ts @@ -17,8 +17,7 @@ export function authenticateFactory(fetchAuth: IFetchAuth): IAuthenticate { * @param {string[] | undefined} userKeys set of user Keys to track MY_SEGMENTS_CHANGES. It is undefined for server-side API. */ return function authenticate(userKeys?: string[]): Promise { - let authPromise = fetchAuth(userKeys); // errors handled by fetchAuth service - return authPromise + return fetchAuth(userKeys) .then(resp => resp.json()) .then(json => { if (json.token) { // empty token when `"pushEnabled": false` diff --git a/src/sync/streaming/__tests__/pushManager.spec.ts b/src/sync/streaming/__tests__/pushManager.spec.ts index 26b63f7d..99b75473 100644 --- a/src/sync/streaming/__tests__/pushManager.spec.ts +++ b/src/sync/streaming/__tests__/pushManager.spec.ts @@ -1,7 +1,10 @@ -import { pushManagerFactory } from '../pushManager'; import { EventEmitter } from '../../../utils/MinEvents'; -import { fullSettings } from '../../../utils/settingsValidation/__tests__/settings.mocks'; +import { fullSettings, fullSettingsServerSide } from '../../../utils/settingsValidation/__tests__/settings.mocks'; import { IPushManagerCS } from '../types'; +import { syncTaskFactory } from '../../__tests__/syncTask.mock'; + +// Test target +import { pushManagerFactory } from '../pushManager'; const platformMock = { getEventSource: jest.fn(() => { return () => { }; }), @@ -18,19 +21,149 @@ test('pushManagerFactory returns undefined if EventSource is not available', () expect(pushManager).toBe(undefined); }); -test('pushManager does not connect to streaming if it is stopped inmediatelly after being started', (done) => { - const fetchAuthMock = jest.fn(); +describe('pushManager in client-side', () => { - // @ts-ignore - const pushManager = pushManagerFactory({}, {}, {}, fetchAuthMock, platformMock, fullSettings) as IPushManagerCS; + test('does not connect to streaming if it is stopped inmediatelly after being started', async () => { + const fetchAuthMock = jest.fn(); + + // @ts-ignore + const pushManager = pushManagerFactory({}, {}, {}, fetchAuthMock, platformMock, fullSettings) as IPushManagerCS; + + pushManager.start(); + pushManager.start(); + pushManager.stop(); + pushManager.stop(); + pushManager.start(); + pushManager.stop(); + + await new Promise(res => setTimeout(res)); + expect(fetchAuthMock).not.toBeCalled(); + }); + + test('re-authenticates asynchronously when it is resumed and when new users are added', async () => { + + // @TODO assert log messages + const fetchAuthMock = jest.fn(() => Promise.reject({ message: 'error' })); + + // @ts-ignore + const pushManager = pushManagerFactory({}, {}, {}, fetchAuthMock, platformMock, { + ...fullSettings, + scheduler: { + ...fullSettings.scheduler, + pushRetryBackoffBase: 10 // high authentication backoff + } + }) as IPushManagerCS; + + // calling start multiple times has no effect (authenticates only once) + pushManager.start(); + pushManager.start(); + + // authenticates asynchronously, only once for both users + const mySegmentsSyncTask = syncTaskFactory(); + pushManager.add('user2', mySegmentsSyncTask); + expect(fetchAuthMock).toHaveBeenCalledTimes(0); + await new Promise(res => setTimeout(res)); + expect(fetchAuthMock).toHaveBeenLastCalledWith([fullSettings.core.key, 'user2']); + + // re-authenticates asynchronously due to new users + pushManager.add('user3', mySegmentsSyncTask); + pushManager.add('user4', mySegmentsSyncTask); + expect(fetchAuthMock).toHaveBeenCalledTimes(1); + await new Promise(res => setTimeout(res)); + expect(fetchAuthMock).toHaveBeenLastCalledWith([fullSettings.core.key, 'user2', 'user3', 'user4']); + + // pausing + pushManager.stop(); + pushManager.stop(); + await new Promise(res => setTimeout(res)); + expect(fetchAuthMock).toHaveBeenCalledTimes(2); - pushManager.start(); - pushManager.start(); - pushManager.stop(); - pushManager.stop(); + // re-authenticates asynchronously when resuming + pushManager.start(); + pushManager.start(); + expect(fetchAuthMock).toHaveBeenCalledTimes(2); + await new Promise(res => setTimeout(res)); + expect(fetchAuthMock).toHaveBeenLastCalledWith(['user2', 'user3', 'user4', fullSettings.core.key]); + expect(fetchAuthMock).toHaveBeenCalledTimes(3); - setTimeout(() => { + // doesn't re-authenticate if a user is removed + pushManager.remove('user3'); + expect(fetchAuthMock).toHaveBeenCalledTimes(3); + await new Promise(res => setTimeout(res)); + expect(fetchAuthMock).toHaveBeenCalledTimes(3); + + // re-authenticates asynchronously when resuming, considering the updated list of users + pushManager.stop(); + pushManager.start(); + pushManager.stop(); + pushManager.start(); + await new Promise(res => setTimeout(res)); + expect(fetchAuthMock).toHaveBeenLastCalledWith(['user2', 'user4', fullSettings.core.key]); + expect(fetchAuthMock).toHaveBeenCalledTimes(4); + pushManager.stop(); + }); +}); + +describe('pushManager in server-side', () => { + + test('does not connect to streaming if it is stopped inmediatelly after being started', async () => { + const fetchAuthMock = jest.fn(); + + // @ts-ignore + const pushManager = pushManagerFactory({}, {}, {}, fetchAuthMock, platformMock, fullSettingsServerSide) as IPushManagerCS; + + pushManager.start(); + pushManager.start(); + pushManager.stop(); + pushManager.stop(); + pushManager.start(); + pushManager.stop(); + + await new Promise(res => setTimeout(res)); expect(fetchAuthMock).not.toBeCalled(); - done(); }); + + test('re-authenticates asynchronously when it is resumed', async () => { + const fetchAuthMock = jest.fn(() => Promise.reject({ message: 'error' })); + + // @ts-ignore + const pushManager = pushManagerFactory({}, {}, {}, fetchAuthMock, platformMock, { + ...fullSettingsServerSide, + scheduler: { + ...fullSettingsServerSide.scheduler, + pushRetryBackoffBase: 10 // high authentication backoff + } + }) as IPushManagerCS; + + // calling start multiple times has no effect (authenticates asynchronously only once) + pushManager.start(); + pushManager.start(); + expect(fetchAuthMock).toHaveBeenCalledTimes(0); + await new Promise(res => setTimeout(res)); + expect(fetchAuthMock).toHaveBeenLastCalledWith(undefined); + + // pausing + pushManager.stop(); + pushManager.stop(); + await new Promise(res => setTimeout(res)); + expect(fetchAuthMock).toHaveBeenCalledTimes(1); + + // re-authenticates asynchronously when resuming + pushManager.start(); + pushManager.start(); + expect(fetchAuthMock).toHaveBeenCalledTimes(1); + await new Promise(res => setTimeout(res)); + expect(fetchAuthMock).toHaveBeenCalledTimes(2); + + // re-authenticates asynchronously when stopping/resuming + pushManager.stop(); + pushManager.stop(); + pushManager.start(); + pushManager.start(); + expect(fetchAuthMock).toHaveBeenCalledTimes(2); + await new Promise(res => setTimeout(res)); + expect(fetchAuthMock).toHaveBeenCalledTimes(3); + pushManager.stop(); + }); + }); diff --git a/src/sync/streaming/pushManager.ts b/src/sync/streaming/pushManager.ts index f5130447..55a67549 100644 --- a/src/sync/streaming/pushManager.ts +++ b/src/sync/streaming/pushManager.ts @@ -40,7 +40,7 @@ export function pushManagerFactory( // `userKey` is the matching key of main client in client-side SDK. // It can be used to check if running on client-side or server-side SDK. - const userKey = settings.core.key ? getMatching(settings.core.key) : undefined; // + const userKey = settings.core.key ? getMatching(settings.core.key) : undefined; const log = settings.log; let sseClient: ISSEClient; @@ -59,7 +59,8 @@ export function pushManagerFactory( sseClient.setEventHandler(sseHandler); // init workers - const segmentsUpdateWorker = userKey ? new MySegmentsUpdateWorker(pollingManager.segmentsSyncTask) : new SegmentsUpdateWorker(storage.segments, pollingManager.segmentsSyncTask); + // MySegmentsUpdateWorker (client-side) are initiated in `add` method + const segmentsUpdateWorker = userKey ? undefined : new SegmentsUpdateWorker(storage.segments, pollingManager.segmentsSyncTask); // For server-side we pass the segmentsSyncTask, used by SplitsUpdateWorker to fetch new segments const splitsUpdateWorker = new SplitsUpdateWorker(storage.splits, pollingManager.splitsSyncTask, readiness.splits, userKey ? undefined : pollingManager.segmentsSyncTask); @@ -68,11 +69,6 @@ export function pushManagerFactory( // [Only for client-side] map of user keys to their corresponding hash64 and MySegmentsUpdateWorkers. // Hash64 is used to process MY_SEGMENTS_UPDATE_V2 events and dispatch actions to the corresponding MySegmentsUpdateWorker. const clients: Record = {}; - if (userKey) { - const hash = hashUserKey(userKey); - userKeyHashes[hash] = userKey; - clients[userKey] = { hash64: hash64(userKey), worker: segmentsUpdateWorker as MySegmentsUpdateWorker }; - } // [Only for client-side] variable to flag that a new client was added. It is needed to reconnect streaming. let connectForNewClient = false; @@ -176,7 +172,7 @@ export function pushManagerFactory( function stopWorkers() { splitsUpdateWorker.backoff.reset(); if (userKey) forOwn(clients, ({ worker }) => worker.backoff.reset()); - else segmentsUpdateWorker.backoff.reset(); + else (segmentsUpdateWorker as SegmentsUpdateWorker).backoff.reset(); } pushEmitter.on(PUSH_SUBSYSTEM_DOWN, stopWorkers); @@ -306,37 +302,40 @@ export function pushManagerFactory( // Expose Event Emitter functionality and Event constants Object.create(pushEmitter), { - // Expose functionality for starting and stoping push mode: - stop: disconnectPush, // `handleNonRetryableError` cannot be used as `stop`, because it emits PUSH_SUBSYSTEM_DOWN event, which starts polling. - + // Stop/pause push mode + stop() { + disconnectPush(); // `handleNonRetryableError` cannot be used as `stop`, because it emits PUSH_SUBSYSTEM_DOWN event, which starts polling. + if (userKey) this.remove(userKey); // Necessary to properly resume streaming in client-side (e.g., RN SDK transition to foreground). + }, + // Start/resume push mode start() { // Guard condition to avoid calling `connectPush` again if the `start` method is called multiple times or if push has been disabled. if (disabled || disconnected === false) return; disconnected = false; - // Run in next event-loop cycle for optimization on client-side: if multiple clients are created in the same cycle than the factory, only one authentication is performed. - setTimeout(connectPush); + + if (userKey) this.add(userKey, pollingManager.segmentsSyncTask); // client-side + else setTimeout(connectPush); // server-side runs in next cycle as in client-side, for tests consistency }, // [Only for client-side] add(userKey: string, mySegmentsSyncTask: ISegmentsSyncTask) { - clients[userKey] = { hash64: hash64(userKey), worker: new MySegmentsUpdateWorker(mySegmentsSyncTask) }; - const hash = hashUserKey(userKey); if (!userKeyHashes[hash]) { userKeyHashes[hash] = userKey; + clients[userKey] = { hash64: hash64(userKey), worker: new MySegmentsUpdateWorker(mySegmentsSyncTask) }; connectForNewClient = true; // we must reconnect on start, to listen the channel for the new user key - } - // Reconnects in case of a new client. - // Run in next event-loop cycle to save authentication calls - // in case the user is creating several clients in the current cycle. - setTimeout(function checkForReconnect() { - if (connectForNewClient) { - connectForNewClient = false; - connectPush(); - } - }, 0); + // Reconnects in case of a new client. + // Run in next event-loop cycle to save authentication calls + // in case multiple clients are created in the current cycle. + setTimeout(function checkForReconnect() { + if (connectForNewClient) { + connectForNewClient = false; + connectPush(); + } + }, 0); + } }, // [Only for client-side] remove(userKey: string) { diff --git a/src/sync/syncManagerOnline.ts b/src/sync/syncManagerOnline.ts index c8264106..1b6ec876 100644 --- a/src/sync/syncManagerOnline.ts +++ b/src/sync/syncManagerOnline.ts @@ -49,11 +49,11 @@ export function syncManagerOnlineFactory( /** Sync Manager logic */ function startPolling() { - if (!pollingManager!.isRunning()) { + if (pollingManager!.isRunning()) { + log.info(SYNC_CONTINUE_POLLING); + } else { log.info(SYNC_START_POLLING); pollingManager!.start(); - } else { - log.info(SYNC_CONTINUE_POLLING); } } @@ -81,6 +81,9 @@ export function syncManagerOnlineFactory( * Method used to start the syncManager for the first time, or resume it after being stopped. */ start() { + if (running) return; + running = true; + // start syncing splits and segments if (pollingManager) { if (pushManager) { @@ -96,21 +99,22 @@ export function syncManagerOnlineFactory( } // start periodic data recording (events, impressions, telemetry). - submitter && submitter.start(); - running = true; + if (submitter) submitter.start(); }, /** * Method used to stop/pause the syncManager. */ stop() { + if (!running) return; + running = false; + // stop syncing if (pushManager) pushManager.stop(); if (pollingManager && pollingManager.isRunning()) pollingManager.stop(); // stop periodic data recording (events, impressions, telemetry). if (submitter) submitter.stop(); - running = false; }, isRunning() { diff --git a/src/utils/settingsValidation/__tests__/settings.mocks.ts b/src/utils/settingsValidation/__tests__/settings.mocks.ts index 80aacabf..80b0392e 100644 --- a/src/utils/settingsValidation/__tests__/settings.mocks.ts +++ b/src/utils/settingsValidation/__tests__/settings.mocks.ts @@ -65,7 +65,7 @@ export const fullSettings: ISettings = { integrations: [() => { }], // A no-op integration mode: 'standalone', debug: false, - streamingEnabled: false, + streamingEnabled: true, sync: { splitFilters: [], impressionsMode: 'OPTIMIZED', @@ -90,6 +90,15 @@ export const fullSettings: ISettings = { log: loggerMock }; +export const fullSettingsServerSide = { + ...fullSettings, + core: { + ...fullSettings.core, + key: undefined, + }, + features: '.split' +}; + export const settingsSplitApi = { core: { authorizationKey: 'api-key' From fbf619226ab0a719e3442e51ab0d592960fa4b96 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 10 Feb 2022 18:31:28 -0300 Subject: [PATCH 2/5] CI update to use node v14, which doesn't close on unhandled promise rejections on tests --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1fac0178..277879c2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: - name: Set up nodejs uses: actions/setup-node@v2 with: - node-version: 'lts/*' + node-version: '14' cache: 'npm' - name: npm CI From bd923146e1e6da8a918f380c979f38a75ced4b1f Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 10 Feb 2022 20:24:40 -0300 Subject: [PATCH 3/5] polishing --- src/sync/__tests__/syncTask.spec.ts | 2 +- src/sync/streaming/__tests__/pushManager.spec.ts | 11 +++++++---- src/sync/streaming/pushManager.ts | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/sync/__tests__/syncTask.spec.ts b/src/sync/__tests__/syncTask.spec.ts index d4a7502b..49ecf67f 100644 --- a/src/sync/__tests__/syncTask.spec.ts +++ b/src/sync/__tests__/syncTask.spec.ts @@ -64,7 +64,7 @@ test('syncTaskFactory', (done) => { syncTask.stop(); expect(asyncTask).toBeCalledTimes(7); - // // Resume periodic execution + // Resume periodic execution syncTask.start(); // Inmediatelly call task syncTask.start(); // No effect expect(asyncTask).toBeCalledTimes(8); diff --git a/src/sync/streaming/__tests__/pushManager.spec.ts b/src/sync/streaming/__tests__/pushManager.spec.ts index 99b75473..824caa63 100644 --- a/src/sync/streaming/__tests__/pushManager.spec.ts +++ b/src/sync/streaming/__tests__/pushManager.spec.ts @@ -41,8 +41,6 @@ describe('pushManager in client-side', () => { }); test('re-authenticates asynchronously when it is resumed and when new users are added', async () => { - - // @TODO assert log messages const fetchAuthMock = jest.fn(() => Promise.reject({ message: 'error' })); // @ts-ignore @@ -54,7 +52,9 @@ describe('pushManager in client-side', () => { } }) as IPushManagerCS; - // calling start multiple times has no effect (authenticates only once) + // calling start again has no effect (authenticates asynchronously only once) + pushManager.start(); + pushManager.stop(); pushManager.start(); pushManager.start(); @@ -135,9 +135,12 @@ describe('pushManager in server-side', () => { } }) as IPushManagerCS; - // calling start multiple times has no effect (authenticates asynchronously only once) + // calling start again has no effect (authenticates asynchronously only once) pushManager.start(); pushManager.start(); + // @TODO pausing & resuming synchronously is not working as expected in server-side + // pushManager.stop(); + // pushManager.start(); expect(fetchAuthMock).toHaveBeenCalledTimes(0); await new Promise(res => setTimeout(res)); expect(fetchAuthMock).toHaveBeenLastCalledWith(undefined); diff --git a/src/sync/streaming/pushManager.ts b/src/sync/streaming/pushManager.ts index 55a67549..c004db0c 100644 --- a/src/sync/streaming/pushManager.ts +++ b/src/sync/streaming/pushManager.ts @@ -314,7 +314,7 @@ export function pushManagerFactory( disconnected = false; if (userKey) this.add(userKey, pollingManager.segmentsSyncTask); // client-side - else setTimeout(connectPush); // server-side runs in next cycle as in client-side, for tests consistency + else setTimeout(connectPush); // server-side runs in next cycle as in client-side, for consistency with client-side }, // [Only for client-side] From 8821ad11337fdfd971c094ea9d9d2b32b71ba0bf Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Fri, 11 Feb 2022 10:22:36 -0300 Subject: [PATCH 4/5] remove guard conditions from SyncManager start/stop. They are not required because polling and push manager operations are idempotent --- src/sync/syncManagerOnline.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/sync/syncManagerOnline.ts b/src/sync/syncManagerOnline.ts index 1b6ec876..8c62466d 100644 --- a/src/sync/syncManagerOnline.ts +++ b/src/sync/syncManagerOnline.ts @@ -81,9 +81,6 @@ export function syncManagerOnlineFactory( * Method used to start the syncManager for the first time, or resume it after being stopped. */ start() { - if (running) return; - running = true; - // start syncing splits and segments if (pollingManager) { if (pushManager) { @@ -100,21 +97,20 @@ export function syncManagerOnlineFactory( // start periodic data recording (events, impressions, telemetry). if (submitter) submitter.start(); + running = true; }, /** * Method used to stop/pause the syncManager. */ stop() { - if (!running) return; - running = false; - // stop syncing if (pushManager) pushManager.stop(); if (pollingManager && pollingManager.isRunning()) pollingManager.stop(); // stop periodic data recording (events, impressions, telemetry). if (submitter) submitter.stop(); + running = false; }, isRunning() { From fedb5aafb478120412abc03c01c5fafde6f84448 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Sun, 13 Feb 2022 13:29:02 -0300 Subject: [PATCH 5/5] Removed some types and interfaces, to clean up code --- src/evaluator/matchers/ew.ts | 8 +-- .../polling/fetchers/mySegmentsFetcher.ts | 3 +- src/sync/polling/fetchers/types.ts | 1 + src/sync/polling/pollingManagerCS.ts | 9 +-- src/sync/polling/pollingManagerSS.ts | 11 +-- .../polling/syncTasks/mySegmentsSyncTask.ts | 3 +- src/sync/polling/types.ts | 12 ---- .../polling/updaters/mySegmentsUpdater.ts | 3 +- .../UpdateWorkers/SegmentsUpdateWorker.ts | 2 +- .../__tests__/SegmentsUpdateWorker.spec.ts | 2 +- .../streaming/__tests__/pushManager.spec.ts | 72 +++++++++++-------- src/sync/streaming/pushManager.ts | 22 +++--- src/sync/streaming/types.ts | 30 ++------ src/sync/submitters/submitterManager.ts | 12 ++-- src/sync/syncManagerOnline.ts | 30 ++++---- src/utils/inputValidation/eventProperties.ts | 6 +- src/utils/lang/__tests__/index.spec.ts | 7 +- src/utils/lang/index.ts | 14 ---- 18 files changed, 98 insertions(+), 149 deletions(-) diff --git a/src/evaluator/matchers/ew.ts b/src/evaluator/matchers/ew.ts index 851e0e2c..6afdc9e1 100644 --- a/src/evaluator/matchers/ew.ts +++ b/src/evaluator/matchers/ew.ts @@ -1,13 +1,13 @@ import { ENGINE_MATCHER_ENDS_WITH } from '../../logger/constants'; import { ILogger } from '../../logger/types'; -import { endsWith as strEndsWith } from '../../utils/lang'; +import { endsWith } from '../../utils/lang'; export function endsWithMatcherContext(log: ILogger, ruleAttr: string[]) /*: Function */ { return function endsWithMatcher(runtimeAttr: string): boolean { - let endsWith = ruleAttr.some(e => strEndsWith(runtimeAttr, e)); + let strEndsWith = ruleAttr.some(e => endsWith(runtimeAttr, e)); - log.debug(ENGINE_MATCHER_ENDS_WITH, [runtimeAttr, ruleAttr, endsWith]); + log.debug(ENGINE_MATCHER_ENDS_WITH, [runtimeAttr, ruleAttr, strEndsWith]); - return endsWith; + return strEndsWith; }; } diff --git a/src/sync/polling/fetchers/mySegmentsFetcher.ts b/src/sync/polling/fetchers/mySegmentsFetcher.ts index 8ea7a5f4..498132b0 100644 --- a/src/sync/polling/fetchers/mySegmentsFetcher.ts +++ b/src/sync/polling/fetchers/mySegmentsFetcher.ts @@ -6,9 +6,10 @@ import { IMySegmentsFetcher } from './types'; * Factory of MySegments fetcher. * MySegments fetcher is a wrapper around `mySegments` API service that parses the response and handle errors. */ -export function mySegmentsFetcherFactory(fetchMySegments: IFetchMySegments, userMatchingKey: string): IMySegmentsFetcher { +export function mySegmentsFetcherFactory(fetchMySegments: IFetchMySegments): IMySegmentsFetcher { return function mySegmentsFetcher( + userMatchingKey: string, noCache?: boolean, // Optional decorator for `fetchMySegments` promise, such as timeout or time tracker decorator?: (promise: Promise) => Promise diff --git a/src/sync/polling/fetchers/types.ts b/src/sync/polling/fetchers/types.ts index 4d1272f0..e15331fb 100644 --- a/src/sync/polling/fetchers/types.ts +++ b/src/sync/polling/fetchers/types.ts @@ -15,6 +15,7 @@ export type ISegmentChangesFetcher = ( ) => Promise export type IMySegmentsFetcher = ( + userMatchingKey: string, noCache?: boolean, decorator?: (promise: Promise) => Promise ) => Promise diff --git a/src/sync/polling/pollingManagerCS.ts b/src/sync/polling/pollingManagerCS.ts index 138ed003..364829a5 100644 --- a/src/sync/polling/pollingManagerCS.ts +++ b/src/sync/polling/pollingManagerCS.ts @@ -1,26 +1,23 @@ import { ISegmentsSyncTask, ISplitsSyncTask, IPollingManagerCS } from './types'; import { forOwn } from '../../utils/lang'; import { IReadinessManager } from '../../readiness/types'; -import { ISplitApi } from '../../services/types'; import { IStorageSync } from '../../storages/types'; import { mySegmentsSyncTaskFactory } from './syncTasks/mySegmentsSyncTask'; import { splitsSyncTaskFactory } from './syncTasks/splitsSyncTask'; -import { ISettings } from '../../types'; import { getMatching } from '../../utils/key'; import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED } from '../../readiness/constants'; import { POLLING_SMART_PAUSING, POLLING_START, POLLING_STOP } from '../../logger/constants'; +import { ISyncManagerFactoryParams } from '../types'; /** * Expose start / stop mechanism for polling data from services. * For client-side API with multiple clients. */ export function pollingManagerCSFactory( - splitApi: ISplitApi, - storage: IStorageSync, - readiness: IReadinessManager, - settings: ISettings, + params: ISyncManagerFactoryParams ): IPollingManagerCS { + const { splitApi, storage, readiness, settings } = params; const log = settings.log; const splitsSyncTask: ISplitsSyncTask = splitsSyncTaskFactory(splitApi.fetchSplitChanges, storage, readiness, settings); diff --git a/src/sync/polling/pollingManagerSS.ts b/src/sync/polling/pollingManagerSS.ts index c1e7edad..2c7d3ec9 100644 --- a/src/sync/polling/pollingManagerSS.ts +++ b/src/sync/polling/pollingManagerSS.ts @@ -1,23 +1,18 @@ import { splitsSyncTaskFactory } from './syncTasks/splitsSyncTask'; import { segmentsSyncTaskFactory } from './syncTasks/segmentsSyncTask'; -import { IStorageSync } from '../../storages/types'; -import { IReadinessManager } from '../../readiness/types'; -import { ISplitApi } from '../../services/types'; -import { ISettings } from '../../types'; import { IPollingManager, ISegmentsSyncTask, ISplitsSyncTask } from './types'; import { thenable } from '../../utils/promise/thenable'; import { POLLING_START, POLLING_STOP, LOG_PREFIX_SYNC_POLLING } from '../../logger/constants'; +import { ISyncManagerFactoryParams } from '../types'; /** * Expose start / stop mechanism for pulling data from services. */ export function pollingManagerSSFactory( - splitApi: ISplitApi, - storage: IStorageSync, - readiness: IReadinessManager, - settings: ISettings + params: ISyncManagerFactoryParams ): IPollingManager { + const { splitApi, storage, readiness, settings } = params; const log = settings.log; const splitsSyncTask: ISplitsSyncTask = splitsSyncTaskFactory(splitApi.fetchSplitChanges, storage, readiness, settings); diff --git a/src/sync/polling/syncTasks/mySegmentsSyncTask.ts b/src/sync/polling/syncTasks/mySegmentsSyncTask.ts index 7b830d21..30f521b0 100644 --- a/src/sync/polling/syncTasks/mySegmentsSyncTask.ts +++ b/src/sync/polling/syncTasks/mySegmentsSyncTask.ts @@ -21,12 +21,13 @@ export function mySegmentsSyncTaskFactory( settings.log, mySegmentsUpdaterFactory( settings.log, - mySegmentsFetcherFactory(fetchMySegments, matchingKey), + mySegmentsFetcherFactory(fetchMySegments), storage.splits, storage.segments, readiness.segments, settings.startup.requestTimeoutBeforeReady, settings.startup.retriesOnFailureBeforeReady, + matchingKey ), settings.scheduler.segmentsRefreshRate, 'mySegmentsUpdater', diff --git a/src/sync/polling/types.ts b/src/sync/polling/types.ts index f3cf3639..3908152a 100644 --- a/src/sync/polling/types.ts +++ b/src/sync/polling/types.ts @@ -1,7 +1,5 @@ import { IReadinessManager } from '../../readiness/types'; -import { ISplitApi } from '../../services/types'; import { IStorageSync } from '../../storages/types'; -import { ISettings } from '../../types'; import { SegmentsData } from '../streaming/SSEHandler/types'; import { ITask, ISyncTask } from '../types'; @@ -23,13 +21,3 @@ export interface IPollingManagerCS extends IPollingManager { remove(matchingKey: string): void; get(matchingKey: string): ISegmentsSyncTask | undefined } - -/** - * Signature of polling manager factory/constructor - */ -export type IPollingManagerFactoryParams = [ - splitApi: ISplitApi, - storage: IStorageSync, - readiness: IReadinessManager, - settings: ISettings, -] diff --git a/src/sync/polling/updaters/mySegmentsUpdater.ts b/src/sync/polling/updaters/mySegmentsUpdater.ts index a72ee54b..486cbe00 100644 --- a/src/sync/polling/updaters/mySegmentsUpdater.ts +++ b/src/sync/polling/updaters/mySegmentsUpdater.ts @@ -23,6 +23,7 @@ export function mySegmentsUpdaterFactory( segmentsEventEmitter: ISegmentsEventEmitter, requestTimeoutBeforeReady: number, retriesOnFailureBeforeReady: number, + matchingKey: string ): IMySegmentsUpdater { let readyOnAlreadyExistentState = true; @@ -69,7 +70,7 @@ export function mySegmentsUpdaterFactory( // If segmentsData is provided, there is no need to fetch mySegments new Promise((res) => { updateSegments(segmentsData); res(true); }) : // If not provided, fetch mySegments - mySegmentsFetcher(noCache, _promiseDecorator).then(segments => { + mySegmentsFetcher(matchingKey, noCache, _promiseDecorator).then(segments => { // Only when we have downloaded segments completely, we should not keep retrying anymore startingUp = false; diff --git a/src/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.ts b/src/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.ts index 5c52872d..03889a25 100644 --- a/src/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.ts +++ b/src/sync/streaming/UpdateWorkers/SegmentsUpdateWorker.ts @@ -19,7 +19,7 @@ export class SegmentsUpdateWorker implements IUpdateWorker { * @param {Object} segmentsCache segments data cache * @param {Object} segmentsSyncTask task for syncing segments data */ - constructor(segmentsCache: ISegmentsCacheSync, segmentsSyncTask: ISegmentsSyncTask) { + constructor(segmentsSyncTask: ISegmentsSyncTask, segmentsCache: ISegmentsCacheSync) { this.segmentsCache = segmentsCache; this.segmentsSyncTask = segmentsSyncTask; this.maxChangeNumbers = {}; diff --git a/src/sync/streaming/UpdateWorkers/__tests__/SegmentsUpdateWorker.spec.ts b/src/sync/streaming/UpdateWorkers/__tests__/SegmentsUpdateWorker.spec.ts index 4a494657..e3168593 100644 --- a/src/sync/streaming/UpdateWorkers/__tests__/SegmentsUpdateWorker.spec.ts +++ b/src/sync/streaming/UpdateWorkers/__tests__/SegmentsUpdateWorker.spec.ts @@ -47,7 +47,7 @@ describe('SegmentsUpdateWorker ', () => { cache.addToSegment('mocked_segment_3', ['e']); const segmentsSyncTask = segmentsSyncTaskMock(cache); - const segmentsUpdateWorker = new SegmentsUpdateWorker(cache, segmentsSyncTask); + const segmentsUpdateWorker = new SegmentsUpdateWorker(segmentsSyncTask, cache); segmentsUpdateWorker.backoff.baseMillis = 0; // retry immediately expect(segmentsUpdateWorker.maxChangeNumbers).toEqual({}); // inits with not queued events; diff --git a/src/sync/streaming/__tests__/pushManager.spec.ts b/src/sync/streaming/__tests__/pushManager.spec.ts index 824caa63..72d8c225 100644 --- a/src/sync/streaming/__tests__/pushManager.spec.ts +++ b/src/sync/streaming/__tests__/pushManager.spec.ts @@ -1,24 +1,34 @@ import { EventEmitter } from '../../../utils/MinEvents'; import { fullSettings, fullSettingsServerSide } from '../../../utils/settingsValidation/__tests__/settings.mocks'; -import { IPushManagerCS } from '../types'; import { syncTaskFactory } from '../../__tests__/syncTask.mock'; // Test target import { pushManagerFactory } from '../pushManager'; +import { IPushManager } from '../types'; -const platformMock = { - getEventSource: jest.fn(() => { return () => { }; }), - EventEmitter +const paramsMock = { + platform: { + getEventSource: jest.fn(() => { return () => { }; }), + EventEmitter + }, + settings: fullSettings, + storage: {}, + readiness: {} }; test('pushManagerFactory returns undefined if EventSource is not available', () => { - // @ts-ignore - const pushManager = pushManagerFactory({}, {}, {}, () => { }, { - getEventSource: () => undefined, - EventEmitter - }, fullSettings); - - expect(pushManager).toBe(undefined); + const params = { + ...paramsMock, + platform: { + getEventSource: () => undefined, + EventEmitter + } + }; // @ts-ignore + const pushManagerCS = pushManagerFactory(params, {}); // @ts-ignore + const pushManagerSS = pushManagerFactory(params, {}); + + expect(pushManagerCS).toBe(undefined); + expect(pushManagerSS).toBe(undefined); }); describe('pushManager in client-side', () => { @@ -26,8 +36,9 @@ describe('pushManager in client-side', () => { test('does not connect to streaming if it is stopped inmediatelly after being started', async () => { const fetchAuthMock = jest.fn(); - // @ts-ignore - const pushManager = pushManagerFactory({}, {}, {}, fetchAuthMock, platformMock, fullSettings) as IPushManagerCS; + const pushManager = pushManagerFactory({ // @ts-ignore + ...paramsMock, splitApi: { fetchAuth: fetchAuthMock } + }, {}) as IPushManager; pushManager.start(); pushManager.start(); @@ -43,14 +54,15 @@ describe('pushManager in client-side', () => { test('re-authenticates asynchronously when it is resumed and when new users are added', async () => { const fetchAuthMock = jest.fn(() => Promise.reject({ message: 'error' })); - // @ts-ignore - const pushManager = pushManagerFactory({}, {}, {}, fetchAuthMock, platformMock, { - ...fullSettings, - scheduler: { - ...fullSettings.scheduler, - pushRetryBackoffBase: 10 // high authentication backoff + const pushManager = pushManagerFactory({ // @ts-ignore + ...paramsMock, splitApi: { fetchAuth: fetchAuthMock }, settings: { + ...fullSettings, + scheduler: { + ...fullSettings.scheduler, + pushRetryBackoffBase: 10 // high authentication backoff + } } - }) as IPushManagerCS; + }, {}) as IPushManager; // calling start again has no effect (authenticates asynchronously only once) pushManager.start(); @@ -109,8 +121,9 @@ describe('pushManager in server-side', () => { test('does not connect to streaming if it is stopped inmediatelly after being started', async () => { const fetchAuthMock = jest.fn(); - // @ts-ignore - const pushManager = pushManagerFactory({}, {}, {}, fetchAuthMock, platformMock, fullSettingsServerSide) as IPushManagerCS; + const pushManager = pushManagerFactory({ // @ts-ignore + ...paramsMock, splitApi: { fetchAuth: fetchAuthMock }, settings: fullSettingsServerSide + }, {}) as IPushManager; pushManager.start(); pushManager.start(); @@ -126,14 +139,15 @@ describe('pushManager in server-side', () => { test('re-authenticates asynchronously when it is resumed', async () => { const fetchAuthMock = jest.fn(() => Promise.reject({ message: 'error' })); - // @ts-ignore - const pushManager = pushManagerFactory({}, {}, {}, fetchAuthMock, platformMock, { - ...fullSettingsServerSide, - scheduler: { - ...fullSettingsServerSide.scheduler, - pushRetryBackoffBase: 10 // high authentication backoff + const pushManager = pushManagerFactory({ // @ts-ignore + ...paramsMock, splitApi: { fetchAuth: fetchAuthMock }, settings: { + ...fullSettingsServerSide, + scheduler: { + ...fullSettingsServerSide.scheduler, + pushRetryBackoffBase: 10 // high authentication backoff + } } - }) as IPushManagerCS; + }, {}) as IPushManager; // calling start again has no effect (authenticates asynchronously only once) pushManager.start(); diff --git a/src/sync/streaming/pushManager.ts b/src/sync/streaming/pushManager.ts index c004db0c..fc3a6ad2 100644 --- a/src/sync/streaming/pushManager.ts +++ b/src/sync/streaming/pushManager.ts @@ -1,7 +1,5 @@ -import { IPushEventEmitter, IPushManagerCS } from './types'; +import { IPushEventEmitter, IPushManager } from './types'; import { ISSEClient } from './SSEClient/types'; -import { IStorageSync } from '../../storages/types'; -import { IReadinessManager } from '../../readiness/types'; import { ISegmentsSyncTask, IPollingManager } from '../polling/types'; import { objectAssign } from '../../utils/lang/objectAssign'; import { Backoff } from '../../utils/Backoff'; @@ -12,17 +10,15 @@ import { SplitsUpdateWorker } from './UpdateWorkers/SplitsUpdateWorker'; import { authenticateFactory, hashUserKey } from './AuthClient'; import { forOwn } from '../../utils/lang'; import { SSEClient } from './SSEClient'; -import { IFetchAuth } from '../../services/types'; -import { ISettings } from '../../types'; import { getMatching } from '../../utils/key'; 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'; -import { IPlatform } from '../../sdkFactory/types'; 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'; import { KeyList, UpdateStrategy } from './SSEHandler/types'; import { isInBitmap, parseBitmap, parseKeyList } from './mySegmentsV2utils'; import { ISet, _Set } from '../../utils/lang/sets'; import { Hash64, hash64 } from '../../utils/murmur3/murmur3_64'; import { IAuthTokenPushEnabled } from './AuthClient/types'; +import { ISyncManagerFactoryParams } from '../types'; /** * PushManager factory: @@ -30,13 +26,11 @@ import { IAuthTokenPushEnabled } from './AuthClient/types'; * - for client-side, with support for multiple clients, if key is provided in settings */ export function pushManagerFactory( + params: ISyncManagerFactoryParams, pollingManager: IPollingManager, - storage: IStorageSync, - readiness: IReadinessManager, - fetchAuth: IFetchAuth, - platform: IPlatform, - settings: ISettings, -): IPushManagerCS | undefined { +): IPushManager | undefined { + + const { settings, storage, splitApi, readiness, platform } = params; // `userKey` is the matching key of main client in client-side SDK. // It can be used to check if running on client-side or server-side SDK. @@ -51,7 +45,7 @@ export function pushManagerFactory( log.warn(STREAMING_FALLBACK, [e]); return; } - const authenticate = authenticateFactory(fetchAuth); + const authenticate = authenticateFactory(splitApi.fetchAuth); // init feedback loop const pushEmitter = new platform.EventEmitter() as IPushEventEmitter; @@ -60,7 +54,7 @@ export function pushManagerFactory( // init workers // MySegmentsUpdateWorker (client-side) are initiated in `add` method - const segmentsUpdateWorker = userKey ? undefined : new SegmentsUpdateWorker(storage.segments, pollingManager.segmentsSyncTask); + const segmentsUpdateWorker = userKey ? undefined : new SegmentsUpdateWorker(pollingManager.segmentsSyncTask, storage.segments); // For server-side we pass the segmentsSyncTask, used by SplitsUpdateWorker to fetch new segments const splitsUpdateWorker = new SplitsUpdateWorker(storage.splits, pollingManager.splitsSyncTask, readiness.splits, userKey ? undefined : pollingManager.segmentsSyncTask); diff --git a/src/sync/streaming/types.ts b/src/sync/streaming/types.ts index 6832a335..57a4a96a 100644 --- a/src/sync/streaming/types.ts +++ b/src/sync/streaming/types.ts @@ -1,11 +1,7 @@ import { IMySegmentsUpdateData, IMySegmentsUpdateV2Data, ISegmentUpdateData, ISplitUpdateData, ISplitKillData } from './SSEHandler/types'; import { ITask } from '../types'; -import { IPollingManager, ISegmentsSyncTask } from '../polling/types'; -import { IReadinessManager } from '../../readiness/types'; -import { IFetchAuth } from '../../services/types'; -import { IStorageSync } from '../../storages/types'; -import { IEventEmitter, ISettings } from '../../types'; -import { IPlatform } from '../../sdkFactory/types'; +import { ISegmentsSyncTask } from '../polling/types'; +import { IEventEmitter } from '../../types'; import { ControlType } from './constants'; // Internal SDK events, subscribed by SyncManager and PushManager @@ -45,26 +41,10 @@ export interface IPushEventEmitter extends IEventEmitter { } /** - * PushManager for server-side + * PushManager */ -export interface IPushManager extends ITask, IPushEventEmitter { } - -/** - * PushManager for client-side with support for multiple clients - */ -export interface IPushManagerCS extends IPushManager { +export interface IPushManager extends ITask, IPushEventEmitter { + // Methods used in client-side, to support multiple clients add(userKey: string, mySegmentsSyncTask: ISegmentsSyncTask): void, remove(userKey: string): void } - -/** - * Signature of push manager factory/constructor - */ -export type IPushManagerFactoryParams = [ - pollingManager: IPollingManager, - storage: IStorageSync, - readiness: IReadinessManager, - fetchAuth: IFetchAuth, - platform: IPlatform, - settings: ISettings -] diff --git a/src/sync/submitters/submitterManager.ts b/src/sync/submitters/submitterManager.ts index 7828d947..51440a25 100644 --- a/src/sync/submitters/submitterManager.ts +++ b/src/sync/submitters/submitterManager.ts @@ -2,15 +2,11 @@ import { syncTaskComposite } from '../syncTaskComposite'; import { eventsSyncTaskFactory } from './eventsSyncTask'; import { impressionsSyncTaskFactory } from './impressionsSyncTask'; import { impressionCountsSyncTaskFactory } from './impressionCountsSyncTask'; -import { ISplitApi } from '../../services/types'; -import { IStorageSync } from '../../storages/types'; -import { ISettings } from '../../types'; +import { ISyncManagerFactoryParams } from '../types'; -export function submitterManagerFactory( - settings: ISettings, - storage: IStorageSync, - splitApi: ISplitApi, -) { +export function submitterManagerFactory(params: ISyncManagerFactoryParams) { + + const { settings, storage, splitApi } = params; const log = settings.log; const submitters = [ impressionsSyncTaskFactory(log, splitApi.postTestImpressionsBulk, storage.impressions, settings.scheduler.impressionsRefreshRate, settings.core.labelsEnabled), diff --git a/src/sync/syncManagerOnline.ts b/src/sync/syncManagerOnline.ts index 8c62466d..1bbfe948 100644 --- a/src/sync/syncManagerOnline.ts +++ b/src/sync/syncManagerOnline.ts @@ -2,8 +2,8 @@ import { ISyncManagerCS, ISyncManagerFactoryParams } from './types'; import { submitterManagerFactory } from './submitters/submitterManager'; import { IReadinessManager } from '../readiness/types'; import { IStorageSync } from '../storages/types'; -import { IPushManagerFactoryParams, IPushManager, IPushManagerCS } from './streaming/types'; -import { IPollingManager, IPollingManagerCS, IPollingManagerFactoryParams } from './polling/types'; +import { IPushManager } from './streaming/types'; +import { IPollingManager, IPollingManagerCS } from './polling/types'; import { PUSH_SUBSYSTEM_UP, PUSH_SUBSYSTEM_DOWN } from './streaming/constants'; import { SYNC_START_POLLING, SYNC_CONTINUE_POLLING, SYNC_STOP_POLLING } from '../logger/constants'; @@ -16,34 +16,28 @@ import { SYNC_START_POLLING, SYNC_CONTINUE_POLLING, SYNC_STOP_POLLING } from '.. * @param pushManagerFactory optional to build a SyncManager with or without streaming support */ export function syncManagerOnlineFactory( - pollingManagerFactory?: (...args: IPollingManagerFactoryParams) => IPollingManager, - pushManagerFactory?: (...args: IPushManagerFactoryParams) => IPushManager | undefined + pollingManagerFactory?: (params: ISyncManagerFactoryParams) => IPollingManager, + pushManagerFactory?: (params: ISyncManagerFactoryParams, pollingManager: IPollingManager) => IPushManager | undefined, ): (params: ISyncManagerFactoryParams) => ISyncManagerCS { /** * SyncManager factory for modular SDK */ - return function ({ - settings, - platform, - splitApi, - storage, - readiness - }: ISyncManagerFactoryParams): ISyncManagerCS { + return function (params: ISyncManagerFactoryParams): ISyncManagerCS { - const log = settings.log; + const { log, streamingEnabled } = params.settings; /** Polling Manager */ - const pollingManager = pollingManagerFactory && pollingManagerFactory(splitApi, storage, readiness, settings); + const pollingManager = pollingManagerFactory && pollingManagerFactory(params); /** Push Manager */ - const pushManager = settings.streamingEnabled && pollingManager && pushManagerFactory ? - pushManagerFactory(pollingManager, storage, readiness, splitApi.fetchAuth, platform, settings) : + const pushManager = streamingEnabled && pollingManager && pushManagerFactory ? + pushManagerFactory(params, pollingManager) : undefined; /** Submitter Manager */ // It is not inyected as push and polling managers, because at the moment it is required - const submitter = submitterManagerFactory(settings, storage, splitApi); + const submitter = submitterManagerFactory(params); /** Sync Manager logic */ @@ -141,7 +135,7 @@ export function syncManagerOnlineFactory( // of segments since `syncAll` was already executed when starting the main client mySegmentsSyncTask.execute(); } - (pushManager as IPushManagerCS).add(matchingKey, mySegmentsSyncTask); + pushManager.add(matchingKey, mySegmentsSyncTask); } else { if (storage.splits.usesSegments()) mySegmentsSyncTask.start(); } @@ -151,7 +145,7 @@ export function syncManagerOnlineFactory( const mySegmentsSyncTask = (pollingManager as IPollingManagerCS).get(matchingKey); if (mySegmentsSyncTask) { // stop syncing - if (pushManager) (pushManager as IPushManagerCS).remove(matchingKey); + if (pushManager) pushManager.remove(matchingKey); if (mySegmentsSyncTask.isRunning()) mySegmentsSyncTask.stop(); (pollingManager as IPollingManagerCS).remove(matchingKey); diff --git a/src/utils/inputValidation/eventProperties.ts b/src/utils/inputValidation/eventProperties.ts index b2bf20e6..1fb2984e 100644 --- a/src/utils/inputValidation/eventProperties.ts +++ b/src/utils/inputValidation/eventProperties.ts @@ -1,4 +1,5 @@ -import { isObject, shallowClone, isString, isFiniteNumber, isBoolean } from '../lang'; +import { isObject, isString, isFiniteNumber, isBoolean } from '../lang'; +import { objectAssign } from '../lang/objectAssign'; import { SplitIO } from '../../types'; import { ILogger } from '../../logger/types'; 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 } const keys = Object.keys(maybeProperties); - const clone = shallowClone(maybeProperties); + // Shallow clone + const clone = objectAssign({}, maybeProperties); // To avoid calculating the size twice we'll return it from here. const output = { properties: clone, diff --git a/src/utils/lang/__tests__/index.spec.ts b/src/utils/lang/__tests__/index.spec.ts index 613ad573..e6e724a1 100644 --- a/src/utils/lang/__tests__/index.spec.ts +++ b/src/utils/lang/__tests__/index.spec.ts @@ -18,11 +18,11 @@ import { toNumber, forOwn, groupBy, - shallowClone, isBoolean } from '../index'; import { getFnName } from '../getFnName'; import { getGlobal } from '../getGlobal'; +import { objectAssign } from '../objectAssign'; test('LANG UTILS / startsWith', () => { expect(startsWith('myStr', 'myS')).toBe(true); @@ -529,7 +529,7 @@ test('LANG UTILS / getFnName', () => { }); -test('LANG UTILS / shallowClone', () => { +test('LANG UTILS / objectAssign', () => { const toClone = { aProperty: 1, another: 'two', @@ -539,7 +539,7 @@ test('LANG UTILS / shallowClone', () => { bool: true }; - const clone = shallowClone(toClone); + const clone = objectAssign({}, toClone); expect(clone).toEqual(toClone); // The structure of the shallow clone should be the same since references are copied too. expect(clone).not.toBe(toClone); // But the reference to the object itself is differente since it is a clone @@ -558,4 +558,3 @@ test('LANG UTILS / isBoolean', () => { [true, false].forEach(val => expect(isBoolean(val)).toBe(true)); }); - diff --git a/src/utils/lang/index.ts b/src/utils/lang/index.ts index 340d3ccc..f894f652 100644 --- a/src/utils/lang/index.ts +++ b/src/utils/lang/index.ts @@ -194,20 +194,6 @@ export function merge(target: { [key: string]: any }, source: { [key: string]: a return res; } -/** - * Shallow clone an object - */ -export function shallowClone(obj: any): any { - const keys = Object.keys(obj); - const output: Record = {}; - - for (let i = 0; i < keys.length; i++) { - output[keys[i]] = obj[keys[i]]; - } - - return output; -} - /** * Checks if the target string starts with the sub string. */