Skip to content

Commit 6241b0f

Browse files
Extract SDK lifecycle methods (init, flush, destroy) into separate module, to reuse by Configs SDK and FF SDK main client.
1 parent 6b495bb commit 6241b0f

4 files changed

Lines changed: 262 additions & 67 deletions

File tree

src/sdkClient/sdkClient.ts

Lines changed: 3 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,16 @@
11
import { objectAssign } from '../utils/lang/objectAssign';
22
import SplitIO from '../../types/splitio';
3-
import { releaseApiKey, validateAndTrackApiKey } from '../utils/inputValidation/apiKey';
43
import { clientFactory } from './client';
54
import { clientInputValidationDecorator } from './clientInputValidation';
65
import { ISdkFactoryContext } from '../sdkFactory/types';
7-
8-
const COOLDOWN_TIME_IN_MILLIS = 1000;
6+
import { sdkLifecycleFactory } from './sdkLifecycle';
97

108
/**
119
* Creates an Sdk client, i.e., a base client with status, init, flush and destroy interface
1210
*/
1311
export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: boolean): SplitIO.IClient | SplitIO.IAsyncClient {
14-
const { sdkReadinessManager, syncManager, storage, signalListener, settings, telemetryTracker, uniqueKeysTracker } = params;
15-
16-
let hasInit = false;
17-
let lastActionTime = 0;
18-
19-
function __cooldown(func: Function, time: number) {
20-
const now = Date.now();
21-
//get the actual time elapsed in ms
22-
const timeElapsed = now - lastActionTime;
23-
//check if the time elapsed is less than desired cooldown
24-
if (timeElapsed < time) {
25-
//if yes, return message with remaining time in seconds
26-
settings.log.warn(`Flush cooldown, remaining time ${(time - timeElapsed) / 1000} seconds`);
27-
return Promise.resolve();
28-
} else {
29-
//Do the requested action and re-assign the lastActionTime
30-
lastActionTime = now;
31-
return func();
32-
}
33-
}
12+
const { sdkReadinessManager, settings } = params;
3413

35-
function __flush() {
36-
return syncManager ? syncManager.flush() : Promise.resolve();
37-
}
3814

3915
return objectAssign(
4016
// Proto-linkage of the readiness Event Emitter
@@ -48,46 +24,6 @@ export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: bo
4824
params.fallbackTreatmentsCalculator
4925
),
5026

51-
{
52-
init() {
53-
if (hasInit) return;
54-
hasInit = true;
55-
56-
if (!isSharedClient) {
57-
validateAndTrackApiKey(settings.log, settings.core.authorizationKey);
58-
sdkReadinessManager.readinessManager.init();
59-
uniqueKeysTracker.start();
60-
syncManager && syncManager.start();
61-
signalListener && signalListener.start();
62-
}
63-
},
64-
65-
flush() {
66-
// @TODO define cooldown time
67-
return __cooldown(__flush, COOLDOWN_TIME_IN_MILLIS);
68-
},
69-
70-
destroy() {
71-
hasInit = false;
72-
// Mark the SDK as destroyed immediately
73-
sdkReadinessManager.readinessManager.destroy();
74-
75-
// For main client, cleanup the SDK Key, listeners and scheduled jobs, and record stat before flushing data
76-
if (!isSharedClient) {
77-
releaseApiKey(settings.core.authorizationKey);
78-
telemetryTracker.sessionLength();
79-
signalListener && signalListener.stop();
80-
uniqueKeysTracker.stop();
81-
}
82-
83-
// Stop background jobs
84-
syncManager && syncManager.stop();
85-
86-
return __flush().then(() => {
87-
// Cleanup storage
88-
return storage.destroy();
89-
});
90-
}
91-
}
27+
sdkLifecycleFactory(params, isSharedClient)
9228
);
9329
}

src/sdkClient/sdkLifecycle.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import SplitIO from '../../types/splitio';
2+
import { releaseApiKey, validateAndTrackApiKey } from '../utils/inputValidation/apiKey';
3+
import { ISdkFactoryContext } from '../sdkFactory/types';
4+
5+
const COOLDOWN_TIME_IN_MILLIS = 1000;
6+
7+
/**
8+
* Creates an Sdk client, i.e., a base client with status, init, flush and destroy interface
9+
*/
10+
export function sdkLifecycleFactory(params: ISdkFactoryContext, isSharedClient?: boolean): Pick<SplitIO.ConfigSDKClient, 'init' | 'flush' | 'destroy'> {
11+
const { sdkReadinessManager, syncManager, storage, signalListener, settings, telemetryTracker, uniqueKeysTracker } = params;
12+
13+
let hasInit = false;
14+
let lastActionTime = 0;
15+
16+
function __cooldown(func: Function, time: number) {
17+
const now = Date.now();
18+
//get the actual time elapsed in ms
19+
const timeElapsed = now - lastActionTime;
20+
//check if the time elapsed is less than desired cooldown
21+
if (timeElapsed < time) {
22+
//if yes, return message with remaining time in seconds
23+
settings.log.warn(`Flush cooldown, remaining time ${(time - timeElapsed) / 1000} seconds`);
24+
return Promise.resolve();
25+
} else {
26+
//Do the requested action and re-assign the lastActionTime
27+
lastActionTime = now;
28+
return func();
29+
}
30+
}
31+
32+
function __flush() {
33+
return syncManager ? syncManager.flush() : Promise.resolve();
34+
}
35+
36+
return {
37+
init() {
38+
if (hasInit) return;
39+
hasInit = true;
40+
41+
if (!isSharedClient) {
42+
validateAndTrackApiKey(settings.log, settings.core.authorizationKey);
43+
sdkReadinessManager.readinessManager.init();
44+
uniqueKeysTracker.start();
45+
syncManager && syncManager.start();
46+
signalListener && signalListener.start();
47+
}
48+
},
49+
50+
flush() {
51+
// @TODO define cooldown time
52+
return __cooldown(__flush, COOLDOWN_TIME_IN_MILLIS);
53+
},
54+
55+
destroy() {
56+
hasInit = false;
57+
// Mark the SDK as destroyed immediately
58+
sdkReadinessManager.readinessManager.destroy();
59+
60+
// For main client, cleanup the SDK Key, listeners and scheduled jobs, and record stat before flushing data
61+
if (!isSharedClient) {
62+
releaseApiKey(settings.core.authorizationKey);
63+
telemetryTracker.sessionLength();
64+
signalListener && signalListener.stop();
65+
uniqueKeysTracker.stop();
66+
}
67+
68+
// Stop background jobs
69+
syncManager && syncManager.stop();
70+
71+
return __flush().then(() => {
72+
// Cleanup storage
73+
return storage.destroy();
74+
});
75+
}
76+
};
77+
}

src/sdkConfig/index.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { ISdkFactoryContext, ISdkFactoryContextSync, ISdkFactoryParams } from '../sdkFactory/types';
2+
import { sdkReadinessManagerFactory } from '../readiness/sdkReadinessManager';
3+
import { impressionsTrackerFactory } from '../trackers/impressionsTracker';
4+
import { eventTrackerFactory } from '../trackers/eventTracker';
5+
import { telemetryTrackerFactory } from '../trackers/telemetryTracker';
6+
import SplitIO from '../../types/splitio';
7+
import { createLoggerAPI } from '../logger/sdkLogger';
8+
import { NEW_FACTORY } from '../logger/constants';
9+
import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../readiness/constants';
10+
import { objectAssign } from '../utils/lang/objectAssign';
11+
import { strategyDebugFactory } from '../trackers/strategy/strategyDebug';
12+
import { strategyOptimizedFactory } from '../trackers/strategy/strategyOptimized';
13+
import { strategyNoneFactory } from '../trackers/strategy/strategyNone';
14+
import { uniqueKeysTrackerFactory } from '../trackers/uniqueKeysTracker';
15+
import { DEBUG, OPTIMIZED } from '../utils/constants';
16+
import { setRolloutPlan } from '../storages/setRolloutPlan';
17+
import { IStorageSync } from '../storages/types';
18+
import { getMatching } from '../utils/key';
19+
import { FallbackTreatmentsCalculator } from '../evaluator/fallbackTreatmentsCalculator';
20+
import { sdkLifecycleFactory } from '../sdkClient/sdkLifecycle';
21+
22+
/**
23+
* Modular SDK factory
24+
*/
25+
export function sdkConfigFactory(params: ISdkFactoryParams): SplitIO.ConfigSDKClient {
26+
27+
const { settings, platform, storageFactory, splitApiFactory, extraProps,
28+
syncManagerFactory, SignalListener, impressionsObserverFactory,
29+
integrationsManagerFactory,
30+
filterAdapterFactory } = params;
31+
const { log, sync: { impressionsMode }, initialRolloutPlan, core: { key } } = settings;
32+
33+
// @TODO handle non-recoverable errors, such as, global `fetch` not available, invalid SDK Key, etc.
34+
// On non-recoverable errors, we should mark the SDK as destroyed and not start synchronization.
35+
36+
const sdkReadinessManager = sdkReadinessManagerFactory(platform.EventEmitter, settings);
37+
const readiness = sdkReadinessManager.readinessManager;
38+
39+
const storage = storageFactory({
40+
settings,
41+
onReadyCb(error) {
42+
if (error) {
43+
// If storage fails to connect, SDK_READY_TIMED_OUT event is emitted immediately. Review when timeout and non-recoverable errors are reworked
44+
readiness.timeout();
45+
return;
46+
}
47+
readiness.splits.emit(SDK_SPLITS_ARRIVED);
48+
readiness.segments.emit(SDK_SEGMENTS_ARRIVED);
49+
},
50+
onReadyFromCacheCb() {
51+
readiness.splits.emit(SDK_SPLITS_CACHE_LOADED);
52+
}
53+
});
54+
55+
const fallbackTreatmentsCalculator = new FallbackTreatmentsCalculator(settings.fallbackTreatments);
56+
57+
if (initialRolloutPlan) {
58+
setRolloutPlan(log, initialRolloutPlan, storage as IStorageSync, key && getMatching(key));
59+
if ((storage as IStorageSync).splits.getChangeNumber() > -1) readiness.splits.emit(SDK_SPLITS_CACHE_LOADED, { initialCacheLoad: false /* Not an initial load, cache exists */ });
60+
}
61+
62+
const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
63+
const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker });
64+
65+
const observer = impressionsObserverFactory();
66+
const uniqueKeysTracker = uniqueKeysTrackerFactory(log, storage.uniqueKeys, filterAdapterFactory && filterAdapterFactory());
67+
68+
const noneStrategy = strategyNoneFactory(storage.impressionCounts, uniqueKeysTracker);
69+
const strategy = impressionsMode === OPTIMIZED ?
70+
strategyOptimizedFactory(observer, storage.impressionCounts) :
71+
impressionsMode === DEBUG ?
72+
strategyDebugFactory(observer) :
73+
noneStrategy;
74+
75+
const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, noneStrategy, strategy, integrationsManager, storage.telemetry);
76+
const eventTracker = eventTrackerFactory(settings, storage.events, integrationsManager, storage.telemetry);
77+
78+
// splitApi is used by SyncManager and Browser signal listener
79+
const splitApi = splitApiFactory && splitApiFactory(settings, platform, telemetryTracker);
80+
81+
const ctx: ISdkFactoryContext = { clients: {}, splitApi, eventTracker, impressionsTracker, telemetryTracker, uniqueKeysTracker, sdkReadinessManager, readiness, settings, storage, platform, fallbackTreatmentsCalculator };
82+
83+
const syncManager = syncManagerFactory && syncManagerFactory(ctx as ISdkFactoryContextSync);
84+
ctx.syncManager = syncManager;
85+
86+
const signalListener = SignalListener && new SignalListener(syncManager, settings, storage, splitApi);
87+
ctx.signalListener = signalListener;
88+
89+
log.info(NEW_FACTORY, [settings.version]);
90+
91+
return objectAssign(
92+
Object.create(sdkReadinessManager.sdkStatus) as SplitIO.IStatusInterface,
93+
sdkLifecycleFactory(ctx),
94+
{
95+
getConfig(name: string, target?: SplitIO.Target): SplitIO.Config {
96+
return {
97+
value: name + target,
98+
} as SplitIO.Config;
99+
},
100+
101+
track() {
102+
return false;
103+
},
104+
105+
// Logger wrapper API
106+
Logger: createLoggerAPI(log),
107+
108+
settings,
109+
},
110+
extraProps && extraProps(ctx)
111+
);
112+
}

types/splitio.d.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2284,4 +2284,74 @@ declare namespace SplitIO {
22842284
*/
22852285
split(featureFlagName: string): SplitViewAsync;
22862286
}
2287+
2288+
// Configs SDK
2289+
2290+
interface Target extends EvaluationOptions {
2291+
key: SplitKey;
2292+
attributes?: Attributes;
2293+
}
2294+
2295+
interface Config {
2296+
value: any;
2297+
getString(propertyName: string, propertyDefaultValue?: string): string;
2298+
getNumber(propertyName: string, propertyDefaultValue?: number): number;
2299+
getBoolean(propertyName: string, propertyDefaultValue?: boolean): boolean;
2300+
getArray(propertyName: string): ConfigArray;
2301+
getObject(propertyName: string): Config;
2302+
}
2303+
interface ConfigArray {
2304+
value: any;
2305+
getString(index: number, propertyDefaultValue?: string): string;
2306+
getNumber(index: number, propertyDefaultValue?: number): number;
2307+
getBoolean(index: number, propertyDefaultValue?: boolean): boolean;
2308+
getArray(index: number): ConfigArray;
2309+
getObject(index: number): Config;
2310+
}
2311+
2312+
/**
2313+
* Common definitions between SDK instances for different environments interface.
2314+
*/
2315+
interface ConfigSDKClient extends IStatusInterface {
2316+
/**
2317+
* Current settings of the SDK instance.
2318+
*/
2319+
settings: ISettings;
2320+
/**
2321+
* Logger API.
2322+
*/
2323+
Logger: ILoggerAPI;
2324+
/**
2325+
* Initializes the client.
2326+
*/
2327+
init(): void;
2328+
/**
2329+
* Flushes the client.
2330+
*/
2331+
flush(): Promise<void>;
2332+
/**
2333+
* Destroys the client.
2334+
*
2335+
* @returns A promise that resolves once all clients are destroyed.
2336+
*/
2337+
destroy(): Promise<void>;
2338+
/**
2339+
*
2340+
* @param name
2341+
* @param target
2342+
* @returns
2343+
*/
2344+
getConfig(name: string, target?: Target): Config;
2345+
/**
2346+
* Tracks an event to be fed to the results product on Split user interface.
2347+
*
2348+
* @param key - The key that identifies the entity related to this event.
2349+
* @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/}
2350+
* @param eventType - The event type corresponding to this event.
2351+
* @param value - The value of this event.
2352+
* @param properties - The properties of this event. Values can be string, number, boolean or null.
2353+
* @returns Whether the event was added to the queue successfully or not.
2354+
*/
2355+
track(key: SplitKey, trafficType: string, eventType: string, value?: number, properties?: Properties): boolean;
2356+
}
22872357
}

0 commit comments

Comments
 (0)