Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@splitsoftware/splitio-commons",
"version": "1.3.1",
"version": "1.3.2-rc.1",
"description": "Split Javascript SDK common components",
"main": "cjs/index.js",
"module": "esm/index.js",
Expand Down
30 changes: 12 additions & 18 deletions src/sdkFactory/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { ISdkFactoryParams } from './types';
import { ISdkFactoryContext, ISdkFactoryContextSync, ISdkFactoryParams } from './types';
import { sdkReadinessManagerFactory } from '../readiness/sdkReadinessManager';
import { impressionsTrackerFactory } from '../trackers/impressionsTracker';
import { eventTrackerFactory } from '../trackers/eventTracker';
import { telemetryTrackerFactory } from '../trackers/telemetryTracker';
import { IStorageFactoryParams, IStorageSync } from '../storages/types';
import { IStorageFactoryParams } from '../storages/types';
import { SplitIO } from '../types';
import { ISplitApi } from '../services/types';
import { getMatching } from '../utils/key';
import { shouldBeOptimized } from '../trackers/impressionObserver/utils';
import { validateAndTrackApiKey } from '../utils/inputValidation/apiKey';
Expand All @@ -32,7 +31,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
validateAndTrackApiKey(log, settings.core.authorizationKey);

const sdkReadinessManager = sdkReadinessManagerFactory(log, platform.EventEmitter, settings.startup.readyTimeout);
const readinessManager = sdkReadinessManager.readinessManager;
const readiness = sdkReadinessManager.readinessManager;

// @TODO consider passing the settings object, so that each storage access only what it needs
const storageFactoryParams: IStorageFactoryParams = {
Expand All @@ -51,8 +50,8 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
// or partial consumer mode, where it only has submitters, and therefore it doesn't emit readiness events.
onReadyCb: (error) => {
if (error) return; // Don't emit SDK_READY if storage failed to connect. Error message is logged by wrapperAdapter
readinessManager.splits.emit(SDK_SPLITS_ARRIVED);
readinessManager.segments.emit(SDK_SEGMENTS_ARRIVED);
readiness.splits.emit(SDK_SPLITS_ARRIVED);
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
},
metadata: metadataBuilder(settings),
log
Expand All @@ -72,20 +71,15 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
// splitApi is used by SyncManager and Browser signal listener
const splitApi = splitApiFactory && splitApiFactory(settings, platform, telemetryTracker);

const syncManager = syncManagerFactory && syncManagerFactory({
settings,
splitApi: splitApi as ISplitApi,
storage: storage as IStorageSync,
readiness: sdkReadinessManager.readinessManager,
platform,
telemetryTracker
});

// signal listener
const ctx: ISdkFactoryContext = { splitApi, eventTracker, impressionsTracker, telemetryTracker, sdkReadinessManager, readiness, settings, storage, platform };

const syncManager = syncManagerFactory && syncManagerFactory(ctx as ISdkFactoryContextSync);
ctx.syncManager = syncManager;

const signalListener = SignalListener && new SignalListener(syncManager, settings, storage, splitApi);
ctx.signalListener = signalListener;

// Sdk client and manager
const ctx = { eventTracker, impressionsTracker, telemetryTracker, sdkReadinessManager, settings, storage, syncManager, signalListener };
// SDK client and manager
const clientMethod = sdkClientMethodFactory(ctx);
const managerInstance = sdkManagerFactory(log, storage.splits, sdkReadinessManager);

Expand Down
43 changes: 29 additions & 14 deletions src/sdkFactory/types.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,14 @@
import { IIntegrationManager, IIntegrationFactoryParams } from '../integrations/types';
import { ISignalListener } from '../listeners/types';
import { ILogger } from '../logger/types';
import { ISdkReadinessManager } from '../readiness/types';
import { IReadinessManager, ISdkReadinessManager } from '../readiness/types';
import { IFetch, ISplitApi, IEventSourceConstructor } from '../services/types';
import { IStorageAsync, IStorageSync, ISplitsCacheSync, ISplitsCacheAsync, IStorageFactoryParams } from '../storages/types';
import { ISyncManager, ISyncManagerFactoryParams } from '../sync/types';
import { ISyncManager } from '../sync/types';
import { IImpressionObserver } from '../trackers/impressionObserver/types';
import { IImpressionsTracker, IEventTracker, ITelemetryTracker } from '../trackers/types';
import { SplitIO, ISettings, IEventEmitter } from '../types';

export interface ISdkFactoryContext {
storage: IStorageSync | IStorageAsync,
sdkReadinessManager: ISdkReadinessManager,
settings: ISettings
impressionsTracker: IImpressionsTracker,
eventTracker: IEventTracker,
telemetryTracker: ITelemetryTracker
signalListener?: ISignalListener
syncManager?: ISyncManager,
}

/**
* Environment related dependencies.
* These getters are called a fixed number of times per factory instantiation.
Expand All @@ -32,6 +21,32 @@ export interface IPlatform {
now?: () => number
}

export interface ISdkFactoryContext {
platform: IPlatform,
sdkReadinessManager: ISdkReadinessManager,
readiness: IReadinessManager,
settings: ISettings
impressionsTracker: IImpressionsTracker,
eventTracker: IEventTracker,
telemetryTracker: ITelemetryTracker,
storage: IStorageSync | IStorageAsync,
signalListener?: ISignalListener
splitApi?: ISplitApi
syncManager?: ISyncManager,
}

export interface ISdkFactoryContextSync extends ISdkFactoryContext {
storage: IStorageSync,
splitApi: ISplitApi
syncManager: ISyncManager,
}

export interface ISdkFactoryContextAsync extends ISdkFactoryContext {
storage: IStorageAsync,
splitApi: undefined,
syncManager: undefined
}

/**
* Object parameter with the modules required to create an SDK factory instance
*/
Expand All @@ -54,7 +69,7 @@ export interface ISdkFactoryParams {
// SyncManager factory.
// Not required when providing an asynchronous storage (consumer mode), but required in standalone mode to avoid SDK timeout.
// It can create an offline or online sync manager, with or without streaming support.
syncManagerFactory?: (params: ISyncManagerFactoryParams) => ISyncManager,
syncManagerFactory?: (params: ISdkFactoryContextSync) => ISyncManager,

// Sdk manager factory
sdkManagerFactory: (
Expand Down
2 changes: 2 additions & 0 deletions src/storages/inRedis/__tests__/SegmentsCacheInRedis.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ describe('SEGMENTS CACHE IN REDIS', () => {
expect(await cache.isInSegment('mocked-segment', 'd')).toBe(true);
expect(await cache.isInSegment('mocked-segment', 'e')).toBe(true);

await cache.clear();
await connection.quit();
});

Expand All @@ -53,6 +54,7 @@ describe('SEGMENTS CACHE IN REDIS', () => {

['s1', 's2', 's3', 's4'].forEach(s => expect(segments.indexOf(s) !== -1).toBe(true));

await cache.clear();
await connection.quit();
});

Expand Down
7 changes: 4 additions & 3 deletions src/sync/offline/syncManagerOffline.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { ISyncManager, ISyncManagerCS, ISyncManagerFactoryParams } from '../types';
import { ISyncManager, ISyncManagerCS } from '../types';
import { fromObjectSyncTaskFactory } from './syncTasks/fromObjectSyncTask';
import { objectAssign } from '../../utils/lang/objectAssign';
import { ISplitsParser } from './splitsParser/types';
import { IReadinessManager } from '../../readiness/types';
import { SDK_SEGMENTS_ARRIVED } from '../../readiness/constants';
import { ISdkFactoryContextSync } from '../../sdkFactory/types';

function flush() {
return Promise.resolve();
Expand All @@ -17,7 +18,7 @@ function flush() {
*/
export function syncManagerOfflineFactory(
splitsParserFactory: () => ISplitsParser
): (params: ISyncManagerFactoryParams) => ISyncManagerCS {
): (params: ISdkFactoryContextSync) => ISyncManagerCS {

/**
* SyncManager factory for modular SDK
Expand All @@ -26,7 +27,7 @@ export function syncManagerOfflineFactory(
settings,
readiness,
storage,
}: ISyncManagerFactoryParams): ISyncManagerCS {
}: ISdkFactoryContextSync): ISyncManagerCS {

return objectAssign(
fromObjectSyncTaskFactory(splitsParserFactory(), storage, readiness, settings),
Expand Down
4 changes: 2 additions & 2 deletions src/sync/polling/pollingManagerCS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import { splitsSyncTaskFactory } from './syncTasks/splitsSyncTask';
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';
import { ISdkFactoryContextSync } from '../../sdkFactory/types';

/**
* Expose start / stop mechanism for polling data from services.
* For client-side API with multiple clients.
*/
export function pollingManagerCSFactory(
params: ISyncManagerFactoryParams
params: ISdkFactoryContextSync
): IPollingManagerCS {

const { splitApi, storage, readiness, settings } = params;
Expand Down
4 changes: 2 additions & 2 deletions src/sync/polling/pollingManagerSS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { segmentsSyncTaskFactory } from './syncTasks/segmentsSyncTask';
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';
import { ISdkFactoryContextSync } from '../../sdkFactory/types';

/**
* Expose start / stop mechanism for pulling data from services.
*/
export function pollingManagerSSFactory(
params: ISyncManagerFactoryParams
params: ISdkFactoryContextSync
): IPollingManager {

const { splitApi, storage, readiness, settings } = params;
Expand Down
4 changes: 2 additions & 2 deletions src/sync/streaming/pushManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@ 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';
import { TOKEN_REFRESH, AUTH_REJECTION } from '../../utils/constants';
import { ISdkFactoryContextSync } from '../../sdkFactory/types';

/**
* PushManager factory:
* - for server-side if key is not provided in settings.
* - for client-side, with support for multiple clients, if key is provided in settings
*/
export function pushManagerFactory(
params: ISyncManagerFactoryParams,
params: ISdkFactoryContextSync,
pollingManager: IPollingManager,
): IPushManager | undefined {

Expand Down
20 changes: 15 additions & 5 deletions src/sync/submitters/__tests__/eventsSubmitter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,28 @@ import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock';
describe('Events submitter', () => {

let __onFullQueueCb: () => void;
const postEventsBulkMock = jest.fn();
const eventsCacheMock = {
isEmpty: jest.fn(() => true),
setOnFullQueueCb: jest.fn(function (onFullQueueCb) { __onFullQueueCb = onFullQueueCb; })
};
const params = {
settings: {
log: loggerMock,
scheduler: { eventsPushRate: 30000 },
startup: { eventsFirstPushWindow: 0 }
},
splitApi: { postEventsBulkMock: jest.fn() },
storage: { events: eventsCacheMock }
};

beforeEach(() => {
eventsCacheMock.isEmpty.mockClear();
});

test('with eventsFirstPushWindow', async () => {
const eventsFirstPushWindow = 20; // @ts-ignore
const eventsSubmitter = eventsSubmitterFactory(loggerMock, postEventsBulkMock, eventsCacheMock, 30000, eventsFirstPushWindow);
const eventsFirstPushWindow = 20;
params.settings.startup.eventsFirstPushWindow = eventsFirstPushWindow; // @ts-ignore
const eventsSubmitter = eventsSubmitterFactory(params);

eventsSubmitter.start();
expect(eventsSubmitter.isRunning()).toEqual(true); // Submitter should be flagged as running
Expand All @@ -40,8 +49,9 @@ describe('Events submitter', () => {
});

test('without eventsFirstPushWindow', async () => {
// @ts-ignore
const eventsSubmitter = eventsSubmitterFactory(loggerMock, postEventsBulkMock, eventsCacheMock, 30000);
const eventsFirstPushWindow = 0;
params.settings.startup.eventsFirstPushWindow = eventsFirstPushWindow; // @ts-ignore
const eventsSubmitter = eventsSubmitterFactory(params);

eventsSubmitter.start();
expect(eventsSubmitter.isRunning()).toEqual(true); // Submitter should be flagged as running
Expand Down
5 changes: 3 additions & 2 deletions src/sync/submitters/__tests__/telemetrySubmitter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { telemetrySubmitterFactory } from '../telemetrySubmitter';
import { InMemoryStorageFactory } from '../../../storages/inMemory/InMemoryStorage';
import { SDK_READY, SDK_READY_FROM_CACHE } from '../../../readiness/constants';
import { fullSettings } from '../../../utils/settingsValidation/__tests__/settings.mocks';
import { ISyncTask } from '../../types';

describe('Telemetry submitter', () => {

Expand All @@ -26,7 +27,7 @@ describe('Telemetry submitter', () => {
};

test('submits metrics/usage periodically', async () => { // @ts-ignore
const telemetrySubmitter = telemetrySubmitterFactory(params);
const telemetrySubmitter = telemetrySubmitterFactory(params) as ISyncTask;
const popLatenciesSpy = jest.spyOn(params.storage.telemetry!, 'popLatencies');

telemetrySubmitter.start();
Expand All @@ -48,7 +49,7 @@ describe('Telemetry submitter', () => {
});

test('submits metrics/config when SDK is ready', async () => { // @ts-ignore
const telemetrySubmitter = telemetrySubmitterFactory(params);
const telemetrySubmitter = telemetrySubmitterFactory(params) as ISyncTask;
const recordTimeUntilReadyFromCacheSpy = jest.spyOn(params.storage.telemetry!, 'recordTimeUntilReadyFromCache');
const recordTimeUntilReadySpy = jest.spyOn(params.storage.telemetry!, 'recordTimeUntilReady');

Expand Down
22 changes: 10 additions & 12 deletions src/sync/submitters/eventsSubmitter.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,28 @@
import { IEventsCacheSync } from '../../storages/types';
import { IPostEventsBulk } from '../../services/types';
import { submitterFactory, firstPushWindowDecorator } from './submitter';
import { ILogger } from '../../logger/types';
import { SUBMITTERS_PUSH_FULL_QUEUE } from '../../logger/constants';
import { ISdkFactoryContextSync } from '../../sdkFactory/types';

const DATA_NAME = 'events';

/**
* Submitter that periodically posts tracked events
*/
export function eventsSubmitterFactory(
log: ILogger,
postEventsBulk: IPostEventsBulk,
eventsCache: IEventsCacheSync,
eventsPushRate: number,
eventsFirstPushWindow: number,
) {
export function eventsSubmitterFactory(params: ISdkFactoryContextSync) {

const {
settings: { log, scheduler: { eventsPushRate }, startup: { eventsFirstPushWindow } },
splitApi: { postEventsBulk },
storage: { events },
} = params;

// don't retry events.
let submitter = submitterFactory(log, postEventsBulk, eventsCache, eventsPushRate, DATA_NAME);
let submitter = submitterFactory(log, postEventsBulk, events, eventsPushRate, DATA_NAME);

// Set a timer for the first push window of events.
if (eventsFirstPushWindow > 0) submitter = firstPushWindowDecorator(submitter, eventsFirstPushWindow);

// register events submitter to be executed when events cache is full
eventsCache.setOnFullQueueCb(() => {
events.setOnFullQueueCb(() => {
if (submitter.isRunning()) {
log.info(SUBMITTERS_PUSH_FULL_QUEUE, [DATA_NAME]);
submitter.execute();
Expand Down
24 changes: 13 additions & 11 deletions src/sync/submitters/impressionCountsSubmitter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { IPostTestImpressionsCount } from '../../services/types';
import { IImpressionCountsCacheSync } from '../../storages/types';
import { ISdkFactoryContextSync } from '../../sdkFactory/types';
import { submitterFactory } from './submitter';
import { ImpressionCountsPayload } from './types';
import { ILogger } from '../../logger/types';

/**
* Converts `impressionCounts` data from cache into request payload.
Expand Down Expand Up @@ -33,12 +31,16 @@ const IMPRESSIONS_COUNT_RATE = 1800000; // 30 minutes
/**
* Submitter that periodically posts impression counts
*/
export function impressionCountsSubmitterFactory(
log: ILogger,
postTestImpressionsCount: IPostTestImpressionsCount,
impressionCountsCache: IImpressionCountsCacheSync,
) {

// retry impressions counts only once.
return submitterFactory(log, postTestImpressionsCount, impressionCountsCache, IMPRESSIONS_COUNT_RATE, 'impression counts', fromImpressionCountsCollector, 1);
export function impressionCountsSubmitterFactory(params: ISdkFactoryContextSync) {

const {
settings: { log },
splitApi: { postTestImpressionsCount },
storage: { impressionCounts }
} = params;

if (impressionCounts) {
// retry impressions counts only once.
return submitterFactory(log, postTestImpressionsCount, impressionCounts, IMPRESSIONS_COUNT_RATE, 'impression counts', fromImpressionCountsCollector, 1);
}
}
Loading