diff --git a/src/evaluator/types.ts b/src/evaluator/types.ts index bd89dbd3..212509b1 100644 --- a/src/evaluator/types.ts +++ b/src/evaluator/types.ts @@ -22,7 +22,7 @@ export interface IEvaluation { treatment?: string, label: string, changeNumber?: number, - config?: string | null + config?: string | object | null } export type IEvaluationResult = IEvaluation & { treatment: string; impressionsDisabled?: boolean } diff --git a/src/sdkClient/client.ts b/src/sdkClient/client.ts index d29720c3..a4cf6a28 100644 --- a/src/sdkClient/client.ts +++ b/src/sdkClient/client.ts @@ -5,7 +5,7 @@ import { validateDefinitionExistence } from '../utils/inputValidation/definition import { validateTrafficTypeExistence } from '../utils/inputValidation/trafficTypeExistence'; import { SDK_NOT_READY } from '../utils/labels'; import { CONTROL, TREATMENT, TREATMENTS, TREATMENT_WITH_CONFIG, TREATMENTS_WITH_CONFIG, TRACK, TREATMENTS_WITH_CONFIG_BY_FLAGSETS, TREATMENTS_BY_FLAGSETS, TREATMENTS_BY_FLAGSET, TREATMENTS_WITH_CONFIG_BY_FLAGSET, GET_TREATMENTS_WITH_CONFIG, GET_TREATMENTS_BY_FLAG_SETS, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS, GET_TREATMENTS_BY_FLAG_SET, GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET, GET_TREATMENT_WITH_CONFIG, GET_TREATMENT, GET_TREATMENTS, TRACK_FN_LABEL } from '../utils/constants'; -import { IEvaluationResult } from '../evaluator/types'; +import { IEvaluation, IEvaluationResult } from '../evaluator/types'; import SplitIO from '../../types/splitio'; import { IMPRESSION_QUEUEING } from '../logger/constants'; import { ISdkFactoryContext } from '../sdkFactory/types'; @@ -72,7 +72,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl const treatments: SplitIO.Treatments | SplitIO.TreatmentsWithConfig = {}; const properties = stringify(options); Object.keys(evaluationResults).forEach(featureFlagName => { - treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, properties, withConfig, methodName, queue); + treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, properties, withConfig, methodName, queue) as SplitIO.Treatment | SplitIO.TreatmentWithConfig; }); impressionsTracker.track(queue, attributes); @@ -101,7 +101,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl const treatments: SplitIO.Treatments | SplitIO.TreatmentsWithConfig = {}; const properties = stringify(options); Object.keys(evaluationResults).forEach(featureFlagName => { - treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, properties, withConfig, methodName, queue); + treatments[featureFlagName] = processEvaluation(evaluationResults[featureFlagName], featureFlagName, key, properties, withConfig, methodName, queue) as SplitIO.Treatment | SplitIO.TreatmentWithConfig; }); impressionsTracker.track(queue, attributes); @@ -139,7 +139,7 @@ export function clientFactory(params: ISdkFactoryContext): SplitIO.IClient | Spl withConfig: boolean, invokingMethodName: string, queue: ImpressionDecorated[] - ): SplitIO.Treatment | SplitIO.TreatmentWithConfig { + ): SplitIO.Treatment | Pick { const { changeNumber, impressionsDisabled } = evaluation; let { treatment, label, config = null } = evaluation; diff --git a/src/sdkClient/sdkClient.ts b/src/sdkClient/sdkClient.ts index c50b8f72..56ebc8f1 100644 --- a/src/sdkClient/sdkClient.ts +++ b/src/sdkClient/sdkClient.ts @@ -1,40 +1,16 @@ import { objectAssign } from '../utils/lang/objectAssign'; import SplitIO from '../../types/splitio'; -import { releaseApiKey, validateAndTrackApiKey } from '../utils/inputValidation/apiKey'; import { clientFactory } from './client'; import { clientInputValidationDecorator } from './clientInputValidation'; import { ISdkFactoryContext } from '../sdkFactory/types'; - -const COOLDOWN_TIME_IN_MILLIS = 1000; +import { sdkLifecycleFactory } from './sdkLifecycle'; /** * Creates an Sdk client, i.e., a base client with status, init, flush and destroy interface */ export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: boolean): SplitIO.IClient | SplitIO.IAsyncClient { - const { sdkReadinessManager, syncManager, storage, signalListener, settings, telemetryTracker, impressionsTracker } = params; - - let hasInit = false; - let lastActionTime = 0; - - function __cooldown(func: Function, time: number) { - const now = Date.now(); - //get the actual time elapsed in ms - const timeElapsed = now - lastActionTime; - //check if the time elapsed is less than desired cooldown - if (timeElapsed < time) { - //if yes, return message with remaining time in seconds - settings.log.warn(`Flush cooldown, remaining time ${(time - timeElapsed) / 1000} seconds`); - return Promise.resolve(); - } else { - //Do the requested action and re-assign the lastActionTime - lastActionTime = now; - return func(); - } - } + const { sdkReadinessManager, settings } = params; - function __flush() { - return syncManager ? syncManager.flush() : Promise.resolve(); - } return objectAssign( // Proto-linkage of the readiness Event Emitter @@ -48,46 +24,6 @@ export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: bo params.fallbackTreatmentsCalculator ), - { - init() { - if (hasInit) return; - hasInit = true; - - if (!isSharedClient) { - validateAndTrackApiKey(settings.log, settings.core.authorizationKey); - sdkReadinessManager.readinessManager.init(); - impressionsTracker.start(); - syncManager && syncManager.start(); - signalListener && signalListener.start(); - } - }, - - flush() { - // @TODO define cooldown time - return __cooldown(__flush, COOLDOWN_TIME_IN_MILLIS); - }, - - destroy() { - hasInit = false; - // Mark the SDK as destroyed immediately - sdkReadinessManager.readinessManager.destroy(); - - // For main client, cleanup the SDK Key, listeners and scheduled jobs, and record stat before flushing data - if (!isSharedClient) { - releaseApiKey(settings.core.authorizationKey); - telemetryTracker.sessionLength(); - signalListener && signalListener.stop(); - impressionsTracker.stop(); - } - - // Stop background jobs - syncManager && syncManager.stop(); - - return __flush().then(() => { - // Cleanup storage - return storage.destroy(); - }); - } - } + sdkLifecycleFactory(params, isSharedClient) ); } diff --git a/src/sdkClient/sdkLifecycle.ts b/src/sdkClient/sdkLifecycle.ts new file mode 100644 index 00000000..e101c851 --- /dev/null +++ b/src/sdkClient/sdkLifecycle.ts @@ -0,0 +1,77 @@ +import SplitIO from '../../types/splitio'; +import { releaseApiKey, validateAndTrackApiKey } from '../utils/inputValidation/apiKey'; +import { ISdkFactoryContext } from '../sdkFactory/types'; + +const COOLDOWN_TIME_IN_MILLIS = 1000; + +/** + * Creates an Sdk client, i.e., a base client with status, init, flush and destroy interface + */ +export function sdkLifecycleFactory(params: ISdkFactoryContext, isSharedClient?: boolean): Pick { + const { sdkReadinessManager, syncManager, storage, signalListener, settings, telemetryTracker, impressionsTracker } = params; + + let hasInit = false; + let lastActionTime = 0; + + function __cooldown(func: Function, time: number) { + const now = Date.now(); + //get the actual time elapsed in ms + const timeElapsed = now - lastActionTime; + //check if the time elapsed is less than desired cooldown + if (timeElapsed < time) { + //if yes, return message with remaining time in seconds + settings.log.warn(`Flush cooldown, remaining time ${(time - timeElapsed) / 1000} seconds`); + return Promise.resolve(); + } else { + //Do the requested action and re-assign the lastActionTime + lastActionTime = now; + return func(); + } + } + + function __flush() { + return syncManager ? syncManager.flush() : Promise.resolve(); + } + + return { + init() { + if (hasInit) return; + hasInit = true; + + if (!isSharedClient) { + validateAndTrackApiKey(settings.log, settings.core.authorizationKey); + sdkReadinessManager.readinessManager.init(); + impressionsTracker.start(); + syncManager && syncManager.start(); + signalListener && signalListener.start(); + } + }, + + flush() { + // @TODO define cooldown time + return __cooldown(__flush, COOLDOWN_TIME_IN_MILLIS); + }, + + destroy() { + hasInit = false; + // Mark the SDK as destroyed immediately + sdkReadinessManager.readinessManager.destroy(); + + // For main client, cleanup the SDK Key, listeners and scheduled jobs, and record stat before flushing data + if (!isSharedClient) { + releaseApiKey(settings.core.authorizationKey); + telemetryTracker.sessionLength(); + signalListener && signalListener.stop(); + impressionsTracker.stop(); + } + + // Stop background jobs + syncManager && syncManager.stop(); + + return __flush().then(() => { + // Cleanup storage + return storage.destroy(); + }); + } + }; +} diff --git a/src/sdkConfig/configObject.ts b/src/sdkConfig/configObject.ts new file mode 100644 index 00000000..267b0879 --- /dev/null +++ b/src/sdkConfig/configObject.ts @@ -0,0 +1,68 @@ +import SplitIO from '../../types/splitio'; +import { isString } from '../utils/lang'; + +function createConfigObject(value: any): SplitIO.Config { + return { + value, + getString(propertyName: string, propertyDefaultValue?: string): string { + const val = value != null ? value[propertyName] : undefined; + if (typeof val === 'string') return val; + return propertyDefaultValue !== undefined ? propertyDefaultValue : ''; + }, + getNumber(propertyName: string, propertyDefaultValue?: number): number { + const val = value != null ? value[propertyName] : undefined; + if (typeof val === 'number') return val; + return propertyDefaultValue !== undefined ? propertyDefaultValue : 0; + }, + getBoolean(propertyName: string, propertyDefaultValue?: boolean): boolean { + const val = value != null ? value[propertyName] : undefined; + if (typeof val === 'boolean') return val; + return propertyDefaultValue !== undefined ? propertyDefaultValue : false; + }, + getArray(propertyName: string): SplitIO.ConfigArray { + const val = value != null ? value[propertyName] : undefined; + return createConfigArrayObject(Array.isArray(val) ? val : []); + }, + getObject(propertyName: string): SplitIO.Config { + const val = value != null ? value[propertyName] : undefined; + return createConfigObject(val != null && typeof val === 'object' && !Array.isArray(val) ? val : null); + } + }; +} + +function createConfigArrayObject(arr: any[]): SplitIO.ConfigArray { + return { + value: arr, + getString(index: number, propertyDefaultValue?: string): string { + const val = arr[index]; + if (typeof val === 'string') return val; + return propertyDefaultValue !== undefined ? propertyDefaultValue : ''; + }, + getNumber(index: number, propertyDefaultValue?: number): number { + const val = arr[index]; + if (typeof val === 'number') return val; + return propertyDefaultValue !== undefined ? propertyDefaultValue : 0; + }, + getBoolean(index: number, propertyDefaultValue?: boolean): boolean { + const val = arr[index]; + if (typeof val === 'boolean') return val; + return propertyDefaultValue !== undefined ? propertyDefaultValue : false; + }, + getArray(index: number): SplitIO.ConfigArray { + const val = arr[index]; + return createConfigArrayObject(Array.isArray(val) ? val : []); + }, + getObject(index: number): SplitIO.Config { + const val = arr[index]; + return createConfigObject(val != null && typeof val === 'object' && !Array.isArray(val) ? val : null); + } + }; +} + +export function parseConfig(config?: string | object | null): SplitIO.Config { + try { + return createConfigObject(isString(config) ? JSON.parse(config) : config); + } catch { + return createConfigObject(null); + } +} diff --git a/src/sdkConfig/index-ff-wrapper.ts b/src/sdkConfig/index-ff-wrapper.ts new file mode 100644 index 00000000..25222862 --- /dev/null +++ b/src/sdkConfig/index-ff-wrapper.ts @@ -0,0 +1,68 @@ +import { ISdkFactoryParams } from '../sdkFactory/types'; +import { sdkFactory } from '../sdkFactory/index'; +import SplitIO from '../../types/splitio'; +import { objectAssign } from '../utils/lang/objectAssign'; +import { parseConfig } from './configObject'; +import { validateTarget } from '../utils/inputValidation/target'; +import { GET_CONFIG } from '../utils/constants'; +import { ISettings } from '../types'; + +/** + * Configs SDK Client factory implemented as a wrapper over the FF SDK. + * Exposes getConfig and track at the root level instead of requiring a client() call. + * getConfig delegates to getTreatmentWithConfig and wraps the parsed JSON config in a Config object. + */ +export function configsClientFactory(params: ISdkFactoryParams): SplitIO.ConfigsClient { + const ffSdk = sdkFactory({ ...params, lazyInit: true }) as (SplitIO.ISDK | SplitIO.IAsyncSDK) & { init(): void }; + const ffClient = ffSdk.client() as SplitIO.IClient & { init(): void; flush(): Promise }; + const ffManager = ffSdk.manager(); + const log = (ffSdk.settings as ISettings).log; + + return objectAssign( + // Inherit status interface (EventEmitter, Event, getStatus, ready, whenReady, whenReadyFromCache) from ffClient + Object.create(ffClient) as SplitIO.IStatusInterface, + { + settings: ffSdk.settings, + Logger: ffSdk.Logger, + + init() { + ffSdk.init(); + }, + + flush(): Promise { + return ffClient.flush(); + }, + + destroy(): Promise { + return ffSdk.destroy(); + }, + + getConfig(name: string, target?: SplitIO.Target): SplitIO.Config { + if (target) { + // Serve config with target + if (validateTarget(log, target, GET_CONFIG)) { + const result = ffClient.getTreatmentWithConfig(target.key, name, target.attributes) as SplitIO.TreatmentWithConfig; + return parseConfig(result.config); + } else { + log.error('Invalid target for getConfig.'); + } + } + + // Serve config without target + const config = ffManager.split(name) as SplitIO.SplitView; + if (!config) { + log.error('Provided config name does not exist. Serving empty config object.'); + return parseConfig({}); + } + + log.info('Serving default config variant, ' + config.defaultTreatment + ' for config ' + name); + const defaultConfigVariant = config.configs[config.defaultTreatment]; + return parseConfig(defaultConfigVariant); + }, + + track(key: SplitIO.SplitKey, trafficType: string, eventType: string, value?: number, properties?: SplitIO.Properties): boolean { + return ffClient.track(key, trafficType, eventType, value, properties) as boolean; + } + } + ); +} diff --git a/src/sdkConfig/index.ts b/src/sdkConfig/index.ts new file mode 100644 index 00000000..93fad40a --- /dev/null +++ b/src/sdkConfig/index.ts @@ -0,0 +1,87 @@ +import { ISdkFactoryContext, ISdkFactoryContextSync, ISdkFactoryParams } from '../sdkFactory/types'; +import { sdkReadinessManagerFactory } from '../readiness/sdkReadinessManager'; +import { impressionsTrackerFactory } from '../trackers/impressionsTracker'; +import { eventTrackerFactory } from '../trackers/eventTracker'; +import { telemetryTrackerFactory } from '../trackers/telemetryTracker'; +import SplitIO from '../../types/splitio'; +import { createLoggerAPI } from '../logger/sdkLogger'; +import { NEW_FACTORY } from '../logger/constants'; +import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../readiness/constants'; +import { objectAssign } from '../utils/lang/objectAssign'; +import { FallbackTreatmentsCalculator } from '../evaluator/fallbackTreatmentsCalculator'; +import { sdkLifecycleFactory } from '../sdkClient/sdkLifecycle'; + +/** + * Modular SDK factory + */ +export function sdkConfigFactory(params: ISdkFactoryParams): SplitIO.ConfigsClient { + + const { settings, platform, storageFactory, splitApiFactory, extraProps, + syncManagerFactory, SignalListener, integrationsManagerFactory } = params; + const { log } = settings; + + // @TODO handle non-recoverable errors, such as, global `fetch` not available, invalid SDK Key, etc. + // On non-recoverable errors, we should mark the SDK as destroyed and not start synchronization. + + const sdkReadinessManager = sdkReadinessManagerFactory(platform.EventEmitter, settings); + const readiness = sdkReadinessManager.readinessManager; + + const storage = storageFactory({ + settings, + onReadyCb(error) { + if (error) { + // If storage fails to connect, SDK_READY_TIMED_OUT event is emitted immediately. Review when timeout and non-recoverable errors are reworked + readiness.timeout(); + return; + } + readiness.splits.emit(SDK_SPLITS_ARRIVED); + readiness.segments.emit(SDK_SEGMENTS_ARRIVED); + }, + onReadyFromCacheCb() { + readiness.splits.emit(SDK_SPLITS_CACHE_LOADED); + } + }); + + const fallbackTreatmentsCalculator = FallbackTreatmentsCalculator(settings.fallbackTreatments); + + const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now); + const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker }); + + const impressionsTracker = impressionsTrackerFactory(params, storage, integrationsManager); + const eventTracker = eventTrackerFactory(settings, storage.events, integrationsManager, storage.telemetry); + + // splitApi is used by SyncManager and Browser signal listener + const splitApi = splitApiFactory && splitApiFactory(settings, platform, telemetryTracker); + + const ctx: ISdkFactoryContext = { clients: {}, splitApi, eventTracker, impressionsTracker, telemetryTracker, sdkReadinessManager, readiness, settings, storage, platform, fallbackTreatmentsCalculator }; + + const syncManager = syncManagerFactory && syncManagerFactory(ctx as ISdkFactoryContextSync); + ctx.syncManager = syncManager; + + const signalListener = SignalListener && new SignalListener(syncManager, settings, storage, splitApi); + ctx.signalListener = signalListener; + + log.info(NEW_FACTORY, [settings.version]); + + return objectAssign( + Object.create(sdkReadinessManager.sdkStatus) as SplitIO.IStatusInterface, + sdkLifecycleFactory(ctx), + { + getConfig(name: string, target?: SplitIO.Target): SplitIO.Config { + return { + value: name + target, + } as SplitIO.Config; + }, + + track() { + return false; + }, + + // Logger wrapper API + Logger: createLoggerAPI(log), + + settings, + }, + extraProps && extraProps(ctx) + ); +} diff --git a/src/sdkManager/index.ts b/src/sdkManager/index.ts index 8b51a503..21e2b0e6 100644 --- a/src/sdkManager/index.ts +++ b/src/sdkManager/index.ts @@ -29,7 +29,7 @@ function objectToView(splitObject: ISplit | null): SplitIO.SplitView | null { killed: splitObject.killed, changeNumber: splitObject.changeNumber || 0, treatments: collectTreatments(splitObject), - configs: splitObject.configurations || {}, + configs: splitObject.configurations as Record || {}, sets: splitObject.sets || [], defaultTreatment: splitObject.defaultTreatment, impressionsDisabled: splitObject.impressionsDisabled === true, diff --git a/src/storages/KeyBuilderSS.ts b/src/storages/KeyBuilderSS.ts index cf8d2156..238abcad 100644 --- a/src/storages/KeyBuilderSS.ts +++ b/src/storages/KeyBuilderSS.ts @@ -11,6 +11,7 @@ export const METHOD_NAMES: Record = { tfs: 'treatmentsByFlagSets', tcf: 'treatmentsWithConfigByFlagSet', tcfs: 'treatmentsWithConfigByFlagSets', + c: 'config', tr: 'track' }; diff --git a/src/sync/polling/pollingManagerSS.ts b/src/sync/polling/pollingManagerSS.ts index 03ce7f22..a31d654f 100644 --- a/src/sync/polling/pollingManagerSS.ts +++ b/src/sync/polling/pollingManagerSS.ts @@ -1,14 +1,15 @@ import { splitsSyncTaskFactory } from './syncTasks/splitsSyncTask'; import { segmentsSyncTaskFactory } from './syncTasks/segmentsSyncTask'; import { IPollingManager, ISegmentsSyncTask, ISplitsSyncTask } from './types'; -import { POLLING_START, POLLING_STOP, LOG_PREFIX_SYNC_POLLING } from '../../logger/constants'; +import { LOG_PREFIX_SYNC_POLLING, POLLING_START, POLLING_STOP } from '../../logger/constants'; import { ISdkFactoryContextSync } from '../../sdkFactory/types'; /** * Expose start / stop mechanism for pulling data from services. */ export function pollingManagerSSFactory( - params: ISdkFactoryContextSync + params: ISdkFactoryContextSync, + // @TODO ): IPollingManager { const { splitApi, storage, readiness, settings } = params; diff --git a/src/sync/submitters/types.ts b/src/sync/submitters/types.ts index 57e8bfd5..a97debb7 100644 --- a/src/sync/submitters/types.ts +++ b/src/sync/submitters/types.ts @@ -109,16 +109,17 @@ export type LastSync = Partial> export type HttpErrors = Partial> export type HttpLatencies = Partial>> -export type TREATMENT = 't'; -export type TREATMENTS = 'ts'; -export type TREATMENT_WITH_CONFIG = 'tc'; -export type TREATMENTS_WITH_CONFIG = 'tcs'; +export type GET_TREATMENT = 't'; +export type GET_TREATMENTS = 'ts'; +export type GET_TREATMENT_WITH_CONFIG = 'tc'; +export type GET_TREATMENTS_WITH_CONFIG = 'tcs'; export type TRACK = 'tr'; -export type TREATMENTS_BY_FLAGSET = 'tf' -export type TREATMENTS_BY_FLAGSETS = 'tfs' -export type TREATMENTS_WITH_CONFIG_BY_FLAGSET = 'tcf' -export type TREATMENTS_WITH_CONFIG_BY_FLAGSETS = 'tcfs' -export type Method = TREATMENT | TREATMENTS | TREATMENT_WITH_CONFIG | TREATMENTS_WITH_CONFIG | TRACK | TREATMENTS_BY_FLAGSET | TREATMENTS_BY_FLAGSETS | TREATMENTS_WITH_CONFIG_BY_FLAGSET | TREATMENTS_WITH_CONFIG_BY_FLAGSETS; +export type GET_TREATMENTS_BY_FLAGSET = 'tf' +export type GET_TREATMENTS_BY_FLAGSETS = 'tfs' +export type GET_TREATMENTS_WITH_CONFIG_BY_FLAGSET = 'tcf' +export type GET_TREATMENTS_WITH_CONFIG_BY_FLAGSETS = 'tcfs' +export type GET_CONFIG = 'c'; +export type Method = GET_TREATMENT | GET_TREATMENTS | GET_TREATMENT_WITH_CONFIG | GET_TREATMENTS_WITH_CONFIG | TRACK | GET_TREATMENTS_BY_FLAGSET | GET_TREATMENTS_BY_FLAGSETS | GET_TREATMENTS_WITH_CONFIG_BY_FLAGSET | GET_TREATMENTS_WITH_CONFIG_BY_FLAGSETS | GET_CONFIG; export type MethodLatencies = Partial>>; diff --git a/src/utils/constants/index.ts b/src/utils/constants/index.ts index b9c8edc1..352988e3 100644 --- a/src/utils/constants/index.ts +++ b/src/utils/constants/index.ts @@ -47,6 +47,7 @@ export const GET_TREATMENTS_BY_FLAG_SET = 'getTreatmentsByFlagSet'; export const GET_TREATMENTS_BY_FLAG_SETS = 'getTreatmentsByFlagSets'; export const GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SET = 'getTreatmentsWithConfigByFlagSet'; export const GET_TREATMENTS_WITH_CONFIG_BY_FLAG_SETS = 'getTreatmentsWithConfigByFlagSets'; +export const GET_CONFIG = 'getConfig'; export const TRACK_FN_LABEL = 'track'; // Manager method names @@ -85,6 +86,7 @@ export const TREATMENTS_BY_FLAGSET = 'tf'; export const TREATMENTS_BY_FLAGSETS = 'tfs'; export const TREATMENTS_WITH_CONFIG_BY_FLAGSET = 'tcf'; export const TREATMENTS_WITH_CONFIG_BY_FLAGSETS = 'tcfs'; +export const CONFIG = 'c'; export const TRACK = 'tr'; export const CONNECTION_ESTABLISHED = 0; diff --git a/src/utils/inputValidation/target.ts b/src/utils/inputValidation/target.ts new file mode 100644 index 00000000..393e65d7 --- /dev/null +++ b/src/utils/inputValidation/target.ts @@ -0,0 +1,21 @@ +import { isObject } from '../lang'; +import SplitIO from '../../../types/splitio'; +import { ILogger } from '../../logger/types'; +import { validateKey } from './key'; +import { validateAttributes } from './attributes'; +import { ERROR_NOT_PLAIN_OBJECT } from '../../logger/constants'; + +export function validateTarget(log: ILogger, maybeTarget: any, method: string): SplitIO.Target | false { + if (!isObject(maybeTarget)) { + log.error(ERROR_NOT_PLAIN_OBJECT, [method, 'target']); + return false; + } + + const key = validateKey(log, maybeTarget.key, method); + if (key === false) return false; + + const attributes = validateAttributes(log, maybeTarget.attributes, method); + if (attributes === false) return false; + + return { ...maybeTarget, key, attributes }; +} diff --git a/types/splitio.d.ts b/types/splitio.d.ts index b8753566..27577054 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -2284,4 +2284,123 @@ declare namespace SplitIO { */ split(featureFlagName: string): SplitViewAsync; } + + // Configs SDK + + interface Target { + key: SplitKey; + attributes?: Attributes; + } + + interface Config { + value: any; + getString(propertyName: string, propertyDefaultValue?: string): string; + getNumber(propertyName: string, propertyDefaultValue?: number): number; + getBoolean(propertyName: string, propertyDefaultValue?: boolean): boolean; + getArray(propertyName: string): ConfigArray; + getObject(propertyName: string): Config; + } + + interface ConfigArray { + value: any; + getString(index: number, propertyDefaultValue?: string): string; + getNumber(index: number, propertyDefaultValue?: number): number; + getBoolean(index: number, propertyDefaultValue?: boolean): boolean; + getArray(index: number): ConfigArray; + getObject(index: number): Config; + } + + /** + * Configs SDK client interface. + */ + interface ConfigsClient extends IStatusInterface { + /** + * Current settings of the SDK instance. + */ + settings: ISettings; + /** + * Logger API. + */ + Logger: ILoggerAPI; + /** + * Initializes the client. + */ + init(): void; + /** + * Flushes the client. + */ + flush(): Promise; + /** + * Destroys the client. + * + * @returns A promise that resolves once all clients are destroyed. + */ + destroy(): Promise; + /** + * Gets the config object for a given config name and optional target. If no target is provided, the default variant of the config is returned. + * + * @param name - The name of the config we want to get. + * @param target - The target of the config we want to get. + * @returns The config object. + */ + getConfig(name: string, target?: Target, options?: EvaluationOptions): Config; + /** + * Tracks an event to be fed to the results product on Split user interface. + * + * @param key - The key that identifies the entity related to this event. + * @param trafficType - The traffic type of the entity related to this event. See {@link https://developer.harness.io/docs/feature-management-experimentation/management-and-administration/fme-settings/traffic-types/} + * @param eventType - The event type corresponding to this event. + * @param value - The value of this event. + * @param properties - The properties of this event. Values can be string, number, boolean or null. + * @returns Whether the event was added to the queue successfully or not. + */ + track(key: SplitKey, trafficType: string, eventType: string, value?: number, properties?: Properties): boolean; + } + + /** + * Configs SDK client interface with async methods. + */ + interface AsyncConfigsClient extends IStatusInterface { + /** + * Current settings of the SDK instance. + */ + settings: ISettings; + /** + * Logger API. + */ + Logger: ILoggerAPI; + /** + * Initializes the client. + */ + init(): void; + /** + * Flushes the client. + */ + flush(): Promise; + /** + * Destroys the client. + * + * @returns A promise that resolves once all clients are destroyed. + */ + destroy(): Promise; + /** + * Gets the config object for a given config name and optional target. If no target is provided, the default variant of the config is returned. + * + * @param name - The name of the config we want to get. + * @param target - The target of the config we want to get. + * @returns A promise that resolves with the config object. + */ + getConfig(name: string, target?: Target, options?: EvaluationOptions): Promise; + /** + * Tracks an event to be fed to the results product on Split user interface. + * + * @param key - The key that identifies the entity related to this event. + * @param trafficType - The traffic type of the entity related to this event. See {@link https://developer.harness.io/docs/feature-management-experimentation/management-and-administration/fme-settings/traffic-types/} + * @param eventType - The event type corresponding to this event. + * @param value - The value of this event. + * @param properties - The properties of this event. Values can be string, number, boolean or null. + * @returns A promise that resolves with a boolean indicating whether the event was added to the queue successfully or not. + */ + track(key: SplitKey, trafficType: string, eventType: string, value?: number, properties?: Properties): Promise; + } }