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
7 changes: 4 additions & 3 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
1.4.2 (June XX, 2022)
- Updated telemetry to submit data even if user consent is not granted.
- Updated submitters logic, to avoid duplicating the post of impressions to Split cloud when the SDK is destroyed while performing its periodic post of impressions.
1.5.0 (June 29, 2022)
- Added a new config option to control the tasks that listen or poll for updates on feature flags and segments, via the new config sync.enabled . Running online Split will always pull the most recent updates upon initialization, this only affects updates fetching on a running instance. Useful when a consistent session experience is a must or to save resources when updates are not being used.
- Updated telemetry logic to track the anonymous config for user consent flag set to declined or unknown.
- Updated submitters logic, to avoid duplicating the post of impressions to Split cloud when the SDK is destroyed while its periodic post of impressions is running.

1.4.1 (June 13, 2022)
- Bugfixing - Updated submitters logic, to avoid dropping impressions and events that are being tracked while POST request is pending.
Expand Down
4 changes: 2 additions & 2 deletions 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.4.1",
"version": "1.5.0",
"description": "Split Javascript SDK common components",
"main": "cjs/index.js",
"module": "esm/index.js",
Expand Down
18 changes: 9 additions & 9 deletions src/sdkClient/clientAttributesDecoration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { ILogger } from '../logger/types';
import { objectAssign } from '../utils/lang/objectAssign';

/**
* Add in memory attributes storage methods and combine them with any attribute received from the getTreatment/s call
* Add in memory attributes storage methods and combine them with any attribute received from the getTreatment/s call
*/
export function clientAttributesDecoration<TClient extends SplitIO.IClient | SplitIO.IAsyncClient>(log: ILogger, client: TClient) {

Expand Down Expand Up @@ -52,10 +52,10 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl
getTreatments: getTreatments,
getTreatmentsWithConfig: getTreatmentsWithConfig,
track: track,

/**
* Add an attribute to client's in memory attributes storage
*
*
* @param {string} attributeName Attrinute name
* @param {string, number, boolean, list} attributeValue Attribute value
* @returns {boolean} true if the attribute was stored and false otherways
Expand All @@ -70,7 +70,7 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl

/**
* Returns the attribute with the given key
*
*
* @param {string} attributeName Attribute name
* @returns {Object} Attribute with the given key
*/
Expand All @@ -81,7 +81,7 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl

/**
* Add to client's in memory attributes storage the attributes in 'attributes'
*
*
* @param {Object} attributes Object with attributes to store
* @returns true if attributes were stored an false otherways
*/
Expand All @@ -92,7 +92,7 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl

/**
* Return all the attributes stored in client's in memory attributes storage
*
*
* @returns {Object} returns all the stored attributes
*/
getAttributes(): Record<string, Object> {
Expand All @@ -101,8 +101,8 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl

/**
* Removes from client's in memory attributes storage the attribute with the given key
*
* @param {string} attributeName
*
* @param {string} attributeName
* @returns {boolean} true if attribute was removed and false otherways
*/
removeAttribute(attributeName: string) {
Expand All @@ -116,7 +116,7 @@ export function clientAttributesDecoration<TClient extends SplitIO.IClient | Spl
clearAttributes() {
return attributeStorage.clear();
}

});

}
128 changes: 127 additions & 1 deletion src/sync/__tests__/syncManagerOnline.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,46 @@
import { fullSettings } from '../../utils/settingsValidation/__tests__/settings.mocks';
import { syncTaskFactory } from './syncTask.mock';
import { syncManagerOnlineFactory } from '../syncManagerOnline';
import { IReadinessManager } from '../../readiness/types';

jest.mock('../submitters/submitterManager', () => {
return {
submitterManagerFactory: syncTaskFactory
};
});

import { syncManagerOnlineFactory } from '../syncManagerOnline';
// Mocked storageManager
const storageManagerMock = {
splits: {
usesSegments: () => false
}
};

// @ts-expect-error
// Mocked readinessManager
let readinessManagerMock = {
isReady: jest.fn(() => true) // Fake the signal for the non ready SDK
} as IReadinessManager;


// Mocked pollingManager
const pollingManagerMock = {
syncAll: jest.fn(),
start: jest.fn(),
stop: jest.fn(),
isRunning: jest.fn(),
add: jest.fn(()=>{return {isrunning: () => true};}),
get: jest.fn()
};

const pushManagerMock = {
start: jest.fn(),
on: jest.fn(),
stop: jest.fn()
};

// Mocked pushManager
const pushManagerFactoryMock = jest.fn(() => pushManagerMock);

test('syncManagerOnline should start or not the submitter depending on user consent status', () => {
const settings = { ...fullSettings };
Expand Down Expand Up @@ -53,3 +86,96 @@ test('syncManagerOnline should start or not the submitter depending on user cons
expect(submitterManager.execute).lastCalledWith(true); // SubmitterManager should flush only telemetry, if userConsent is unknown

});

test('syncManagerOnline should syncAll a single time when sync is disabled', () => {
const settings = { ...fullSettings };

// disable sync
settings.sync.enabled = false;

// @ts-ignore
// Test pushManager for main client
const syncManager = syncManagerOnlineFactory(() => pollingManagerMock, pushManagerFactoryMock)({ settings });

expect(pushManagerFactoryMock).not.toBeCalled();

// Test pollingManager for Main client
syncManager.start();

expect(pollingManagerMock.start).not.toBeCalled();
expect(pollingManagerMock.syncAll).toBeCalledTimes(1);

syncManager.stop();
syncManager.start();

expect(pollingManagerMock.start).not.toBeCalled();
expect(pollingManagerMock.syncAll).toBeCalledTimes(1);

syncManager.stop();
syncManager.start();

expect(pollingManagerMock.start).not.toBeCalled();
expect(pollingManagerMock.syncAll).toBeCalledTimes(1);

syncManager.stop();

// @ts-ignore
// Test pollingManager for shared client
const pollingSyncManagerShared = syncManager.shared('sharedKey', readinessManagerMock, storageManagerMock);

if (!pollingSyncManagerShared) throw new Error('pollingSyncManagerShared should exist');

pollingSyncManagerShared.start();

expect(pollingManagerMock.start).not.toBeCalled();

pollingSyncManagerShared.stop();
pollingSyncManagerShared.start();

expect(pollingManagerMock.start).not.toBeCalled();

pollingSyncManagerShared.stop();

syncManager.start();

expect(pollingManagerMock.start).not.toBeCalled();

syncManager.stop();
syncManager.start();

expect(pollingManagerMock.start).not.toBeCalled();

syncManager.stop();

// @ts-ignore
// Test pollingManager for shared client
const pushingSyncManagerShared = syncManager.shared('pushingSharedKey', readinessManagerMock, storageManagerMock);

if (!pushingSyncManagerShared) throw new Error('pushingSyncManagerShared should exist');

pushingSyncManagerShared.start();

expect(pollingManagerMock.start).not.toBeCalled();

pushingSyncManagerShared.stop();
pushingSyncManagerShared.start();

expect(pollingManagerMock.start).not.toBeCalled();

pushingSyncManagerShared.stop();

settings.sync.enabled = true;
// @ts-ignore
// pushManager instantiation control test
const testSyncManager = syncManagerOnlineFactory(() => pollingManagerMock, pushManagerFactoryMock)({ settings });

expect(pushManagerFactoryMock).toBeCalled();

// Test pollingManager for Main client
testSyncManager.start();

expect(pushManagerMock.start).toBeCalled();

testSyncManager.stop();

});
45 changes: 29 additions & 16 deletions src/sync/syncManagerOnline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ export function syncManagerOnlineFactory(
*/
return function (params: ISdkFactoryContextSync): ISyncManagerCS {

const { settings, settings: { log, streamingEnabled }, telemetryTracker } = params;
const { settings, settings: { log, streamingEnabled, sync: { enabled: syncEnabled } }, telemetryTracker } = params;

/** Polling Manager */
const pollingManager = pollingManagerFactory && pollingManagerFactory(params);

/** Push Manager */
const pushManager = streamingEnabled && pollingManager && pushManagerFactory ?
const pushManager = syncEnabled && streamingEnabled && pollingManager && pushManagerFactory ?
pushManagerFactory(params, pollingManager) :
undefined;

Expand Down Expand Up @@ -89,15 +89,24 @@ export function syncManagerOnlineFactory(

// start syncing splits and segments
if (pollingManager) {
if (pushManager) {
// Doesn't call `syncAll` when the syncManager is resuming

// If synchronization is disabled pushManager and pollingManager should not start
if (syncEnabled) {
if (pushManager) {
// Doesn't call `syncAll` when the syncManager is resuming
if (startFirstTime) {
pollingManager.syncAll();
startFirstTime = false;
}
pushManager.start();
} else {
pollingManager.start();
}
} else {
if (startFirstTime) {
pollingManager.syncAll();
startFirstTime = false;
}
pushManager.start();
} else {
pollingManager.start();
}
}

Expand Down Expand Up @@ -137,18 +146,22 @@ export function syncManagerOnlineFactory(
return {
isRunning: mySegmentsSyncTask.isRunning,
start() {
if (pushManager) {
if (pollingManager!.isRunning()) {
// if doing polling, we must start the periodic fetch of data
if (storage.splits.usesSegments()) mySegmentsSyncTask.start();
if (syncEnabled) {
if (pushManager) {
if (pollingManager!.isRunning()) {
// if doing polling, we must start the periodic fetch of data
if (storage.splits.usesSegments()) mySegmentsSyncTask.start();
} else {
// if not polling, we must execute the sync task for the initial fetch
// of segments since `syncAll` was already executed when starting the main client
mySegmentsSyncTask.execute();
}
pushManager.add(matchingKey, mySegmentsSyncTask);
} else {
// if not polling, we must execute the sync task for the initial fetch
// of segments since `syncAll` was already executed when starting the main client
mySegmentsSyncTask.execute();
if (storage.splits.usesSegments()) mySegmentsSyncTask.start();
}
pushManager.add(matchingKey, mySegmentsSyncTask);
} else {
if (storage.splits.usesSegments()) mySegmentsSyncTask.start();
if (!readinessManager.isReady()) mySegmentsSyncTask.execute();
}
},
stop() {
Expand Down
8 changes: 7 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@ export interface ISettings {
splitFilters: SplitIO.SplitFilter[],
impressionsMode: SplitIO.ImpressionsMode,
__splitFiltersValidation: ISplitFiltersValidation,
localhostMode?: SplitIO.LocalhostFactory
localhostMode?: SplitIO.LocalhostFactory,
enabled: boolean
},
readonly runtime: {
ip: string | false
Expand Down Expand Up @@ -214,6 +215,11 @@ interface ISharedSettings {
* @default 'OPTIMIZED'
*/
impressionsMode?: SplitIO.ImpressionsMode,
/**
* Enables synchronization.
* @property {boolean} enabled
*/
enabled: boolean
}
}
/**
Expand Down
22 changes: 22 additions & 0 deletions src/utils/settingsValidation/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ describe('settingsValidation', () => {
telemetry: 'https://telemetry.split.io/api',
});
expect(settings.sync.impressionsMode).toBe(OPTIMIZED);
expect(settings.sync.enabled).toBe(true);
});

test('override with default impressionMode if provided one is invalid', () => {
Expand Down Expand Up @@ -163,6 +164,27 @@ describe('settingsValidation', () => {
expect(settingsWithStreamingEnabled.streamingEnabled).toBe(true); // If streamingEnabled is not provided, it will be true.
});

test('sync.enabled should be overwritable and true by default', () => {
const settingsWithSyncEnabled = settingsValidation({
core: {
authorizationKey: 'dummy token',
}
}, minimalSettingsParams);

const settingsWithSyncDisabled = settingsValidation({
core: {
authorizationKey: 'dummy token'
},
sync: {
enabled: false
}
}, minimalSettingsParams);

expect(settingsWithSyncDisabled.sync.enabled).toBe(false); // If sync.enabled is not provided, it will be true.
expect(settingsWithSyncEnabled.sync.enabled).toBe(true); // When creating a setting instance, it will have the provided value for sync.enabled

});

const storageMock = () => { };
const integrationsMock = [() => { }];

Expand Down
3 changes: 2 additions & 1 deletion src/utils/settingsValidation/__tests__/settings.mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ export const fullSettings: ISettings = {
validFilters: [],
queryString: null,
groupedFilters: { byName: [], byPrefix: [] }
}
},
enabled: true
},
version: 'jest',
runtime: {
Expand Down
Loading