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.2.1-rc.11",
"version": "1.2.1-rc.12",
"description": "Split Javascript SDK common components",
"main": "cjs/index.js",
"module": "esm/index.js",
Expand Down
45 changes: 45 additions & 0 deletions src/consent/__tests__/sdkUserConsent.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { createUserConsentAPI } from '../sdkUserConsent';
import { syncTaskFactory } from '../../sync/__tests__/syncTask.mock';
import { fullSettings } from '../../utils/settingsValidation/__tests__/settings.mocks';

test('createUserConsentAPI', () => {
const settings = { ...fullSettings, userConsent: 'UNKNOWN' };
const syncManager = { submitter: syncTaskFactory() };
const storage = {
events: { clear: jest.fn() },
impressions: { clear: jest.fn() }
};

// @ts-ignore
const props = createUserConsentAPI({ settings, syncManager, storage });

// getUserConsent returns settings.userConsent
expect(props.getStatus()).toBe(settings.userConsent);
expect(props.getStatus()).toBe(props.Status.UNKNOWN);

// setting user consent to 'GRANTED'
expect(props.setStatus(true)).toBe(true);
expect(props.setStatus(true)).toBe(true); // calling again has no affect
expect(syncManager.submitter.start).toBeCalledTimes(1); // submitter resumed
expect(syncManager.submitter.stop).toBeCalledTimes(0);
expect(props.getStatus()).toBe(props.Status.GRANTED);

// setting user consent to 'DECLINED'
expect(props.setStatus(false)).toBe(true);
expect(props.setStatus(false)).toBe(true); // calling again has no affect
expect(syncManager.submitter.start).toBeCalledTimes(1);
expect(syncManager.submitter.stop).toBeCalledTimes(1); // submitter paused
expect(props.getStatus()).toBe(props.Status.DECLINED);
expect(storage.events.clear).toBeCalledTimes(1); // storage tracked data dropped
expect(storage.impressions.clear).toBeCalledTimes(1);

// Invalid values have no effect
expect(props.setStatus('DECLINED')).toBe(false); // strings are not valid
expect(props.setStatus('GRANTED')).toBe(false);
expect(props.setStatus(undefined)).toBe(false);
expect(props.setStatus({})).toBe(false);

expect(syncManager.submitter.start).toBeCalledTimes(1);
expect(syncManager.submitter.stop).toBeCalledTimes(1);
expect(props.getStatus()).toBe(props.Status.DECLINED);
});
2 changes: 1 addition & 1 deletion src/utils/consent.ts → src/consent/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ISettings } from '../types';
import { CONSENT_GRANTED } from './constants';
import { CONSENT_GRANTED } from '../utils/constants';

export function isConsentGranted(settings: ISettings) {
const userConsent = settings.userConsent;
Expand Down
58 changes: 58 additions & 0 deletions src/consent/sdkUserConsent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { ERROR_NOT_BOOLEAN, USER_CONSENT_UPDATED, USER_CONSENT_NOT_UPDATED, USER_CONSENT_INITIAL } from '../logger/constants';
import { isConsentGranted } from './index';
import { CONSENT_GRANTED, CONSENT_DECLINED, CONSENT_UNKNOWN } from '../utils/constants';
import { isBoolean } from '../utils/lang';
import { ISdkFactoryContext } from '../sdkFactory/types';

// User consent enum
const ConsentStatus = {
GRANTED: CONSENT_GRANTED,
DECLINED: CONSENT_DECLINED,
UNKNOWN: CONSENT_UNKNOWN,
};

/**
* The public user consent API exposed via SplitFactory, used to control if the SDK tracks and sends impressions and events or not.
*/
export function createUserConsentAPI(params: ISdkFactoryContext) {
const { settings, settings: { log }, syncManager, storage: { events, impressions, impressionCounts } } = params;

if (!isConsentGranted(settings)) log.info(USER_CONSENT_INITIAL, [settings.userConsent]);

return {
setStatus(consent: unknown) {
// validate input param
if (!isBoolean(consent)) {
log.warn(ERROR_NOT_BOOLEAN, ['setUserConsent']);
return false;
}

const newConsentStatus = consent ? CONSENT_GRANTED : CONSENT_DECLINED;

if (settings.userConsent !== newConsentStatus) {
log.info(USER_CONSENT_UPDATED, [settings.userConsent, newConsentStatus]); // @ts-ignore, modify readonly prop
settings.userConsent = newConsentStatus;

if (consent) { // resumes submitters if transitioning to GRANTED
syncManager?.submitter?.start();
} else { // pauses submitters and drops tracked data if transitioning to DECLINED
syncManager?.submitter?.stop();
// @ts-ignore, clear method is present in storage for standalone and partial consumer mode
if (events.clear) events.clear(); // @ts-ignore
if (impressions.clear) impressions.clear();
if (impressionCounts) impressionCounts.clear();
}
} else {
log.info(USER_CONSENT_NOT_UPDATED, [newConsentStatus]);
}

return true;
},

getStatus() {
return settings.userConsent;
},

Status: ConsentStatus
};
}
2 changes: 1 addition & 1 deletion src/listeners/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { OPTIMIZED, DEBUG } from '../utils/constants';
import { objectAssign } from '../utils/lang/objectAssign';
import { CLEANUP_REGISTERING, CLEANUP_DEREGISTERING } from '../logger/constants';
import { ISyncManager } from '../sync/types';
import { isConsentGranted } from '../utils/consent';
import { isConsentGranted } from '../consent';

// 'unload' event is used instead of 'beforeunload', since 'unload' is not a cancelable event, so no other listeners can stop the event from occurring.
const UNLOAD_DOM_EVENT = 'unload';
Expand Down
5 changes: 2 additions & 3 deletions src/sdkClient/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,16 @@ import { validateSplitExistance } from '../utils/inputValidation/splitExistance'
import { validateTrafficTypeExistance } from '../utils/inputValidation/trafficTypeExistance';
import { SDK_NOT_READY } from '../utils/labels';
import { CONTROL } from '../utils/constants';
import { IClientFactoryParams } from './types';
import { IEvaluationResult } from '../evaluator/types';
import { SplitIO, ImpressionDTO } from '../types';
import { IMPRESSION, IMPRESSION_QUEUEING } from '../logger/constants';

import { ISdkFactoryContext } from '../sdkFactory/types';

/**
* Creator of base client with getTreatments and track methods.
*/
// @TODO missing time tracking to collect telemetry
export function clientFactory(params: IClientFactoryParams): SplitIO.IClient | SplitIO.IAsyncClient {
export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | SplitIO.IAsyncClient {
const { sdkReadinessManager: { readinessManager }, storage, settings, impressionsTracker, eventTracker } = params;
const { log, mode } = settings;

Expand Down
8 changes: 4 additions & 4 deletions src/sdkClient/sdkClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { IStatusInterface, SplitIO } from '../types';
import { releaseApiKey } from '../utils/inputValidation/apiKey';
import { clientFactory } from './client';
import { clientInputValidationDecorator } from './clientInputValidation';
import { ISdkClientFactoryParams } from './types';
import { ISdkFactoryContext } from '../sdkFactory/types';

/**
* Creates an Sdk client, i.e., a base client with status and destroy interface
*/
export function sdkClientFactory(params: ISdkClientFactoryParams): SplitIO.IClient | SplitIO.IAsyncClient {
const { sdkReadinessManager, syncManager, storage, signalListener, settings, sharedClient } = params;
export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: boolean): SplitIO.IClient | SplitIO.IAsyncClient {
const { sdkReadinessManager, syncManager, storage, signalListener, settings } = params;

return objectAssign(
// Proto-linkage of the readiness Event Emitter
Expand All @@ -35,7 +35,7 @@ export function sdkClientFactory(params: ISdkClientFactoryParams): SplitIO.IClie
signalListener && signalListener.stop();

// Release the API Key if it is the main client
if (!sharedClient) releaseApiKey(settings.core.authorizationKey);
if (!isSharedClient) releaseApiKey(settings.core.authorizationKey);

// Cleanup storage
return storage.destroy();
Expand Down
4 changes: 2 additions & 2 deletions src/sdkClient/sdkClientMethod.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { ISdkClientFactoryParams } from './types';
import { SplitIO } from '../types';
import { sdkClientFactory } from './sdkClient';
import { RETRIEVE_CLIENT_DEFAULT } from '../logger/constants';
import { ISdkFactoryContext } from '../sdkFactory/types';

/**
* Factory of client method for server-side SDKs (ISDK and IAsyncSDK)
*/
export function sdkClientMethodFactory(params: ISdkClientFactoryParams): () => SplitIO.IClient | SplitIO.IAsyncClient {
export function sdkClientMethodFactory(params: ISdkFactoryContext): () => SplitIO.IClient | SplitIO.IAsyncClient {
const log = params.settings.log;
const clientInstance = sdkClientFactory(params);

Expand Down
9 changes: 4 additions & 5 deletions src/sdkClient/sdkClientMethodCS.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { clientCSDecorator } from './clientCS';
import { ISdkClientFactoryParams } from './types';
import { SplitIO } from '../types';
import { validateKey } from '../utils/inputValidation/key';
import { getMatching, keyParser } from '../utils/key';
Expand All @@ -8,6 +7,7 @@ import { ISyncManagerCS } from '../sync/types';
import { objectAssign } from '../utils/lang/objectAssign';
import { RETRIEVE_CLIENT_DEFAULT, NEW_SHARED_CLIENT, RETRIEVE_CLIENT_EXISTING } from '../logger/constants';
import { SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
import { ISdkFactoryContext } from '../sdkFactory/types';

function buildInstanceId(key: SplitIO.SplitKey) {
// @ts-ignore
Expand All @@ -20,12 +20,12 @@ const method = 'Client instantiation';
* Factory of client method for the client-side API variant where TT is ignored and thus
* clients don't have a binded TT for the track method.
*/
export function sdkClientMethodCSFactory(params: ISdkClientFactoryParams): (key?: SplitIO.SplitKey) => SplitIO.ICsClient {
export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: SplitIO.SplitKey) => SplitIO.ICsClient {
const { storage, syncManager, sdkReadinessManager, settings: { core: { key }, startup: { readyTimeout }, log } } = params;

const mainClientInstance = clientCSDecorator(
log,
sdkClientFactory(params) as SplitIO.IClient, // @ts-ignore
sdkClientFactory(params) as SplitIO.IClient,
key
);

Expand Down Expand Up @@ -76,8 +76,7 @@ export function sdkClientMethodCSFactory(params: ISdkClientFactoryParams): (key?
storage: sharedStorage || storage,
syncManager: sharedSyncManager,
signalListener: undefined, // only the main client "destroy" method stops the signal listener
sharedClient: true
})) as SplitIO.IClient,
}), true) as SplitIO.IClient,
validKey
);

Expand Down
9 changes: 4 additions & 5 deletions src/sdkClient/sdkClientMethodCSWithTT.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { clientCSDecorator } from './clientCS';
import { ISdkClientFactoryParams } from './types';
import { SplitIO } from '../types';
import { validateKey } from '../utils/inputValidation/key';
import { validateTrafficType } from '../utils/inputValidation/trafficType';
Expand All @@ -9,6 +8,7 @@ import { ISyncManagerCS } from '../sync/types';
import { objectAssign } from '../utils/lang/objectAssign';
import { RETRIEVE_CLIENT_DEFAULT, NEW_SHARED_CLIENT, RETRIEVE_CLIENT_EXISTING } from '../logger/constants';
import { SDK_SEGMENTS_ARRIVED } from '../readiness/constants';
import { ISdkFactoryContext } from '../sdkFactory/types';

function buildInstanceId(key: SplitIO.SplitKey, trafficType?: string) {
// @ts-ignore
Expand All @@ -22,12 +22,12 @@ const method = 'Client instantiation';
* where clients can have a binded TT for the track method, which is provided via the settings
* (default client) or the client method (shared clients).
*/
export function sdkClientMethodCSFactory(params: ISdkClientFactoryParams): (key?: SplitIO.SplitKey, trafficType?: string) => SplitIO.ICsClient {
export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: SplitIO.SplitKey, trafficType?: string) => SplitIO.ICsClient {
const { storage, syncManager, sdkReadinessManager, settings: { core: { key, trafficType }, startup: { readyTimeout }, log } } = params;

const mainClientInstance = clientCSDecorator(
log,
sdkClientFactory(params) as SplitIO.IClient, // @ts-ignore
sdkClientFactory(params) as SplitIO.IClient,
key,
trafficType
);
Expand Down Expand Up @@ -86,8 +86,7 @@ export function sdkClientMethodCSFactory(params: ISdkClientFactoryParams): (key?
storage: sharedStorage || storage,
syncManager: sharedSyncManager,
signalListener: undefined, // only the main client "destroy" method stops the signal listener
sharedClient: true
})) as SplitIO.IClient,
}), true) as SplitIO.IClient,
validKey,
validTrafficType
);
Expand Down
21 changes: 0 additions & 21 deletions src/sdkClient/types.ts

This file was deleted.

39 changes: 0 additions & 39 deletions src/sdkFactory/__tests__/userConsentProps.spec.ts

This file was deleted.

5 changes: 3 additions & 2 deletions src/sdkFactory/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
const signalListener = SignalListener && new SignalListener(syncManager, settings, storage, splitApi);

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

syncManager && syncManager.start();
Expand All @@ -104,5 +105,5 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ICsSDK | SplitIO.
Logger: createLoggerAPI(settings.log),

settings,
}, extraProps && extraProps(settings, syncManager));
}, extraProps && extraProps(ctx));
}
16 changes: 13 additions & 3 deletions src/sdkFactory/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@ import { IIntegrationManager, IIntegrationFactoryParams } from '../integrations/
import { ISignalListener } from '../listeners/types';
import { ILogger } from '../logger/types';
import { ISdkReadinessManager } from '../readiness/types';
import { ISdkClientFactoryParams } from '../sdkClient/types';
import { IFetch, ISplitApi, IEventSourceConstructor } from '../services/types';
import { IStorageAsync, IStorageSync, ISplitsCacheSync, ISplitsCacheAsync, IStorageFactoryParams } from '../storages/types';
import { ISyncManager, ISyncManagerFactoryParams } from '../sync/types';
import { IImpressionObserver } from '../trackers/impressionObserver/types';
import { IImpressionsTracker, IEventTracker } from '../trackers/types';
import { SplitIO, ISettings, IEventEmitter } from '../types';

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

/**
* Environment related dependencies.
* These getters are called a fixed number of times per factory instantiation.
Expand Down Expand Up @@ -53,7 +63,7 @@ export interface ISdkFactoryParams {

// Sdk client method factory (ISDK::client method).
// It Allows to distinguish SDK clients with the client-side API (`ICsSDK`) or server-side API (`ISDK` or `IAsyncSDK`).
sdkClientMethodFactory: (params: ISdkClientFactoryParams) => ({ (): SplitIO.ICsClient; (key: SplitIO.SplitKey, trafficType?: string | undefined): SplitIO.ICsClient; } | (() => SplitIO.IClient) | (() => SplitIO.IAsyncClient))
sdkClientMethodFactory: (params: ISdkFactoryContext) => ({ (): SplitIO.ICsClient; (key: SplitIO.SplitKey, trafficType?: string | undefined): SplitIO.ICsClient; } | (() => SplitIO.IClient) | (() => SplitIO.IAsyncClient))

// Optional signal listener constructor. Used to handle special app states, like shutdown, app paused or resumed.
// Pass only if `syncManager` (used by Node listener) and `splitApi` (used by Browser listener) are passed.
Expand All @@ -70,5 +80,5 @@ export interface ISdkFactoryParams {
impressionsObserverFactory?: () => IImpressionObserver

// Optional function to assign additional properties to the factory instance
extraProps?: (settings: ISettings, syncManager?: ISyncManager) => object
extraProps?: (params: ISdkFactoryContext) => object
}
Loading