Skip to content

Commit b102bbf

Browse files
Merge pull request #478 from splitio/refactor-init
Refactor internal `init` and `destroy` methods
2 parents 2e11bce + c5b1483 commit b102bbf

12 files changed

Lines changed: 252 additions & 252 deletions

File tree

.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,11 @@
1212

1313
## coverage info
1414
/coverage
15+
16+
## worktrees
17+
/.worktrees
18+
19+
## agents files
20+
/AGENTS.md
21+
/CLAUDE.md
22+
/.claude

package-lock.json

Lines changed: 185 additions & 174 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,10 @@
6060
"@types/ioredis": "^4.28.0",
6161
"@types/jest": "^27.0.0",
6262
"@types/lodash": "^4.14.162",
63-
"@typescript-eslint/eslint-plugin": "^6.6.0",
64-
"@typescript-eslint/parser": "^6.6.0",
63+
"@typescript-eslint/eslint-plugin": "^7.18.0",
64+
"@typescript-eslint/parser": "^7.18.0",
6565
"cross-env": "^7.0.2",
66-
"eslint": "^8.48.0",
66+
"eslint": "^8.56.0",
6767
"eslint-plugin-compat": "^6.0.1",
6868
"eslint-plugin-import": "^2.25.3",
6969
"eslint-plugin-tsdoc": "^0.3.0",
@@ -76,7 +76,7 @@
7676
"redis-server": "1.2.2",
7777
"rimraf": "^3.0.2",
7878
"ts-jest": "^27.0.5",
79-
"typescript": "4.4.4"
79+
"typescript": "4.7.4"
8080
},
8181
"sideEffects": false
8282
}

src/integrations/pluggable.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export function pluggableIntegrationsManagerFactory(
2121
// No need to check if `settings.integrations` is an array of functions. It was already validated
2222
integrations.forEach(integrationFactory => {
2323
let integration = integrationFactory(params);
24-
if (integration && integration.queue) listeners.push(integration);
24+
if (integration) listeners.push(integration);
2525
});
2626

2727
// If `listeners` is empty, not return a integration manager

src/sdkClient/sdkClient.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
import { objectAssign } from '../utils/lang/objectAssign';
22
import SplitIO from '../../types/splitio';
3-
import { releaseApiKey } from '../utils/inputValidation/apiKey';
3+
import { releaseApiKey, validateAndTrackApiKey } from '../utils/inputValidation/apiKey';
44
import { clientFactory } from './client';
55
import { clientInputValidationDecorator } from './clientInputValidation';
66
import { ISdkFactoryContext } from '../sdkFactory/types';
77

88
const COOLDOWN_TIME_IN_MILLIS = 1000;
99

1010
/**
11-
* Creates an Sdk client, i.e., a base client with status and destroy interface
11+
* Creates an Sdk client, i.e., a base client with status, init, flush and destroy interface
1212
*/
1313
export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: boolean): SplitIO.IClient | SplitIO.IAsyncClient {
1414
const { sdkReadinessManager, syncManager, storage, signalListener, settings, telemetryTracker, uniqueKeysTracker } = params;
1515

16+
let hasInit = false;
1617
let lastActionTime = 0;
1718

1819
function __cooldown(func: Function, time: number) {
@@ -47,13 +48,27 @@ export function sdkClientFactory(params: ISdkFactoryContext, isSharedClient?: bo
4748
params.fallbackTreatmentsCalculator
4849
),
4950

50-
// Sdk destroy
5151
{
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+
5265
flush() {
5366
// @TODO define cooldown time
5467
return __cooldown(__flush, COOLDOWN_TIME_IN_MILLIS);
5568
},
69+
5670
destroy() {
71+
hasInit = false;
5772
// Mark the SDK as destroyed immediately
5873
sdkReadinessManager.readinessManager.destroy();
5974

src/sdkFactory/__tests__/index.spec.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { FallbackTreatmentsCalculator } from '../../evaluator/fallbackTreatments
77

88
/** Mocks */
99

10-
const clientInstance = { destroy: jest.fn() };
10+
const clientInstance = { init: jest.fn(), destroy: jest.fn() };
1111
const managerInstance = 'manager';
1212
const mockStorage = {
1313
splits: jest.fn(),
@@ -40,13 +40,10 @@ const paramsForAsyncSDK = {
4040
fallbackTreatmentsCalculator: new FallbackTreatmentsCalculator()
4141
};
4242

43-
const SignalListenerInstanceMock = { start: jest.fn() };
44-
4543
// IBrowserSDK, full params
4644
const fullParamsForSyncSDK = {
4745
...paramsForAsyncSDK,
4846
syncManagerFactory: jest.fn(),
49-
SignalListener: jest.fn(() => SignalListenerInstanceMock),
5047
impressionsObserverFactory: jest.fn(),
5148
platform: {
5249
getEventSource: jest.fn(),
@@ -79,10 +76,6 @@ function assertModulesCalled(params: any) {
7976
if (params.impressionsObserverFactory) {
8077
expect(params.impressionsObserverFactory).toBeCalledTimes(1);
8178
}
82-
if (params.SignalListener) {
83-
expect(params.SignalListener).toBeCalledTimes(1);
84-
expect(SignalListenerInstanceMock.start).toBeCalledTimes(1);
85-
}
8679
if (params.splitApiFactory) {
8780
expect(params.splitApiFactory.mock.calls).toEqual([[params.settings, params.platform, telemetryTrackerMock]]);
8881
}

src/sdkFactory/index.ts

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { impressionsTrackerFactory } from '../trackers/impressionsTracker';
44
import { eventTrackerFactory } from '../trackers/eventTracker';
55
import { telemetryTrackerFactory } from '../trackers/telemetryTracker';
66
import SplitIO from '../../types/splitio';
7-
import { validateAndTrackApiKey } from '../utils/inputValidation/apiKey';
87
import { createLoggerAPI } from '../logger/sdkLogger';
98
import { NEW_FACTORY, RETRIEVE_MANAGER } from '../logger/constants';
109
import { SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_SPLITS_CACHE_LOADED } from '../readiness/constants';
@@ -33,15 +32,6 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
3332
// @TODO handle non-recoverable errors, such as, global `fetch` not available, invalid SDK Key, etc.
3433
// On non-recoverable errors, we should mark the SDK as destroyed and not start synchronization.
3534

36-
// initialization
37-
let hasInit = false;
38-
const initCallbacks: (() => void)[] = [];
39-
40-
function whenInit(cb: () => void) {
41-
if (hasInit) cb();
42-
else initCallbacks.push(cb);
43-
}
44-
4535
const sdkReadinessManager = sdkReadinessManagerFactory(platform.EventEmitter, settings);
4636
const readiness = sdkReadinessManager.readinessManager;
4737

@@ -68,7 +58,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
6858
if ((storage as IStorageSync).splits.getChangeNumber() > -1) readiness.splits.emit(SDK_SPLITS_CACHE_LOADED, { initialCacheLoad: false /* Not an initial load, cache exists */ });
6959
}
7060

71-
const clients: Record<string, SplitIO.IBasicClient> = {};
61+
const clients: Record<string, SplitIO.IBasicClient & { init: () => void }> = {};
7262
const telemetryTracker = telemetryTrackerFactory(storage.telemetry, platform.now);
7363
const integrationsManager = integrationsManagerFactory && integrationsManagerFactory({ settings, storage, telemetryTracker });
7464

@@ -82,8 +72,8 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
8272
strategyDebugFactory(observer) :
8373
noneStrategy;
8474

85-
const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, noneStrategy, strategy, whenInit, integrationsManager, storage.telemetry);
86-
const eventTracker = eventTrackerFactory(settings, storage.events, whenInit, integrationsManager, storage.telemetry);
75+
const impressionsTracker = impressionsTrackerFactory(settings, storage.impressions, noneStrategy, strategy, integrationsManager, storage.telemetry);
76+
const eventTracker = eventTrackerFactory(settings, storage.events, integrationsManager, storage.telemetry);
8777

8878
// splitApi is used by SyncManager and Browser signal listener
8979
const splitApi = splitApiFactory && splitApiFactory(settings, platform, telemetryTracker);
@@ -93,6 +83,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
9383
const syncManager = syncManagerFactory && syncManagerFactory(ctx as ISdkFactoryContextSync);
9484
ctx.syncManager = syncManager;
9585

86+
// @TODO: move into platform, and call inside sdkClientFactory (if it's used only there)
9687
const signalListener = SignalListener && new SignalListener(syncManager, settings, storage, splitApi);
9788
ctx.signalListener = signalListener;
9889

@@ -102,18 +93,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
10293

10394

10495
function init() {
105-
if (hasInit) return;
106-
hasInit = true;
107-
108-
// We will just log and allow for the SDK to end up throwing an SDK_TIMEOUT event for devs to handle.
109-
validateAndTrackApiKey(log, settings.core.authorizationKey);
110-
readiness.init();
111-
uniqueKeysTracker.start();
112-
syncManager && syncManager.start();
113-
signalListener && signalListener.start();
114-
115-
initCallbacks.forEach((cb) => cb());
116-
initCallbacks.length = 0;
96+
Object.keys(clients).map(key => clients[key].init());
11797
}
11898

11999
log.info(NEW_FACTORY, [settings.version]);
@@ -135,7 +115,6 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
135115
settings,
136116

137117
destroy() {
138-
hasInit = false;
139118
return Promise.all(Object.keys(clients).map(key => clients[key].destroy())).then(() => { });
140119
}
141120
}, extraProps && extraProps(ctx), lazyInit ? { init } : init());

src/sdkFactory/types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,11 @@ export interface ISdkFactoryParams {
9898

9999
// Sdk client method factory.
100100
// It Allows to distinguish SDK clients with the client-side API (`IBrowserSDK` and `IBrowserAsyncSDK`) or server-side API (`ISDK` and `IAsyncSDK`).
101-
sdkClientMethodFactory: (params: ISdkFactoryContext) => ({ (): SplitIO.IBrowserClient; (key: SplitIO.SplitKey): SplitIO.IBrowserClient; } | (() => SplitIO.IClient) | (() => SplitIO.IAsyncClient))
101+
sdkClientMethodFactory: (params: ISdkFactoryContext) => (
102+
{ (): SplitIO.IBrowserClient & { init(): void }; (key: SplitIO.SplitKey): SplitIO.IBrowserClient & { init(): void }; } |
103+
(() => SplitIO.IClient & { init(): void }) |
104+
(() => SplitIO.IAsyncClient & { init(): void })
105+
)
102106

103107
// Impression observer factory.
104108
impressionsObserverFactory: () => IImpressionObserver

src/trackers/__tests__/eventTracker.spec.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,13 @@ const fakeEvent = {
2929
}
3030
};
3131

32-
const fakeWhenInit = (cb: () => void) => cb();
33-
3432
/* Tests */
3533
describe('Event Tracker', () => {
3634

3735
test('Tracker API', () => {
3836
expect(typeof eventTrackerFactory).toBe('function'); // The module should return a function which acts as a factory.
3937

40-
const instance = eventTrackerFactory(fullSettings, fakeEventsCache, fakeWhenInit, fakeIntegrationsManager);
38+
const instance = eventTrackerFactory(fullSettings, fakeEventsCache, fakeIntegrationsManager);
4139

4240
expect(typeof instance.track).toBe('function'); // The instance should implement the track method.
4341
});
@@ -53,7 +51,7 @@ describe('Event Tracker', () => {
5351
}
5452
});
5553
// @ts-ignore
56-
const tracker = eventTrackerFactory(fullSettings, fakeEventsCache, fakeWhenInit, fakeIntegrationsManager, fakeTelemetryCache);
54+
const tracker = eventTrackerFactory(fullSettings, fakeEventsCache, fakeIntegrationsManager, fakeTelemetryCache);
5755
const result1 = tracker.track(fakeEvent, 1);
5856

5957
expect(fakeEventsCache.track.mock.calls[0]).toEqual([fakeEvent, 1]); // Should be present in the event cache.
@@ -94,7 +92,7 @@ describe('Event Tracker', () => {
9492
const settings = { ...fullSettings };
9593
const fakeEventsCache = { track: jest.fn(() => true) };
9694

97-
const tracker = eventTrackerFactory(settings, fakeEventsCache, fakeWhenInit);
95+
const tracker = eventTrackerFactory(settings, fakeEventsCache);
9896

9997
expect(tracker.track(fakeEvent)).toBe(true);
10098
expect(fakeEventsCache.track).toBeCalledTimes(1); // event should be tracked if userConsent is undefined

src/trackers/__tests__/impressionsTracker.spec.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ const fakeSettingsWithListener = {
3434
...fakeSettings,
3535
impressionListener: fakeListener
3636
};
37-
const fakeWhenInit = (cb: () => void) => cb();
38-
3937
const fakeNoneStrategy = {
4038
process: jest.fn(() => false)
4139
};
@@ -53,7 +51,7 @@ describe('Impressions Tracker', () => {
5351
const strategy = strategyDebugFactory(impressionObserverCSFactory());
5452

5553
test('Should be able to track impressions (in DEBUG mode without Previous Time).', () => {
56-
const tracker = impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategy, fakeWhenInit);
54+
const tracker = impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategy);
5755

5856
const imp1 = {
5957
feature: '10',
@@ -73,7 +71,7 @@ describe('Impressions Tracker', () => {
7371
});
7472

7573
test('Tracked impressions should be sent to impression listener and integration manager when we invoke .track()', (done) => {
76-
const tracker = impressionsTrackerFactory(fakeSettingsWithListener, fakeImpressionsCache, fakeNoneStrategy, strategy, fakeWhenInit, fakeIntegrationsManager);
74+
const tracker = impressionsTrackerFactory(fakeSettingsWithListener, fakeImpressionsCache, fakeNoneStrategy, strategy, fakeIntegrationsManager);
7775

7876
const fakeImpression = {
7977
feature: 'impression'
@@ -147,8 +145,8 @@ describe('Impressions Tracker', () => {
147145
impression3.time = 1234567891;
148146

149147
const trackers = [
150-
impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategyDebugFactory(impressionObserverSSFactory()), fakeWhenInit, undefined),
151-
impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategyDebugFactory(impressionObserverCSFactory()), fakeWhenInit, undefined)
148+
impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategyDebugFactory(impressionObserverSSFactory()), undefined),
149+
impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategyDebugFactory(impressionObserverCSFactory()), undefined)
152150
];
153151

154152
expect(fakeImpressionsCache.track).not.toBeCalled(); // storage method should not be called until impressions are tracked.
@@ -175,7 +173,7 @@ describe('Impressions Tracker', () => {
175173
impression3.time = Date.now();
176174

177175
const impressionCountsCache = new ImpressionCountsCacheInMemory();
178-
const tracker = impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategyOptimizedFactory(impressionObserverCSFactory(), impressionCountsCache), fakeWhenInit, undefined, fakeTelemetryCache as any);
176+
const tracker = impressionsTrackerFactory(fakeSettings, fakeImpressionsCache, fakeNoneStrategy, strategyOptimizedFactory(impressionObserverCSFactory(), impressionCountsCache), undefined, fakeTelemetryCache as any);
179177

180178
expect(fakeImpressionsCache.track).not.toBeCalled(); // cache method should not be called by just creating a tracker
181179

@@ -198,7 +196,7 @@ describe('Impressions Tracker', () => {
198196
test('Should track or not impressions depending on user consent status', () => {
199197
const settings = { ...fullSettings };
200198

201-
const tracker = impressionsTrackerFactory(settings, fakeImpressionsCache, fakeNoneStrategy, strategy, fakeWhenInit);
199+
const tracker = impressionsTrackerFactory(settings, fakeImpressionsCache, fakeNoneStrategy, strategy);
202200

203201
tracker.track([{ imp: impression }]);
204202
expect(fakeImpressionsCache.track).toBeCalledTimes(1); // impression should be tracked if userConsent is undefined

0 commit comments

Comments
 (0)