Skip to content

Commit

Permalink
feat: [do not re-init and perform a network request if the singleton … (
Browse files Browse the repository at this point in the history
#95)

* feat: [do not re-init and perform a network request if the singleton has already been init] (FF-2921)

* Adjust unit tests

* update log
  • Loading branch information
leoromanovsky authored Aug 5, 2024
1 parent be41540 commit c34f847
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 3 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@eppo/js-client-sdk",
"version": "3.4.0",
"version": "3.5.0",
"description": "Eppo SDK for client-side JavaScript applications",
"main": "dist/index.js",
"files": [
Expand Down Expand Up @@ -61,4 +61,4 @@
"dependencies": {
"@eppo/js-client-sdk-common": "4.0.0"
}
}
}
65 changes: 65 additions & 0 deletions src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ describe('EppoJSClient E2E test', () => {
apiKey,
baseUrl,
assignmentLogger: mockLogger,
forceReinitialize: true,
});
});

Expand Down Expand Up @@ -335,6 +336,7 @@ describe('EppoJSClient E2E test', () => {
apiKey,
baseUrl,
assignmentLogger: mockLogger,
forceReinitialize: true,
});
});

Expand Down Expand Up @@ -494,6 +496,61 @@ describe('initialization options', () => {
expect(callCount).toBe(2);
});

it('do not reinitialize if already initialized', async () => {
let callCount = 0;

global.fetch = jest.fn(() => {
callCount += 1;
return Promise.resolve({
ok: true,
status: 200,
json: () => Promise.resolve(mockConfigResponse),
});
}) as jest.Mock;

await init({
apiKey,
baseUrl,
assignmentLogger: mockLogger,
});

await init({
apiKey,
baseUrl,
assignmentLogger: mockLogger,
});

expect(callCount).toBe(1);
});

it('force reinitialize', async () => {
let callCount = 0;

global.fetch = jest.fn(() => {
callCount += 1;
return Promise.resolve({
ok: true,
status: 200,
json: () => Promise.resolve(mockConfigResponse),
});
}) as jest.Mock;

await init({
apiKey,
baseUrl,
assignmentLogger: mockLogger,
});

await init({
apiKey,
baseUrl,
assignmentLogger: mockLogger,
forceReinitialize: true,
});

expect(callCount).toBe(2);
});

it('polls after successful init if configured to do so', async () => {
let callCount = 0;

Expand Down Expand Up @@ -723,6 +780,7 @@ describe('initialization options', () => {
apiKey,
baseUrl,
assignmentLogger: mockLogger,
forceReinitialize: true,
});

expect(fetchCallCount).toBe(1);
Expand All @@ -735,6 +793,7 @@ describe('initialization options', () => {
baseUrl: 'https://thisisabaddomainforthistest.com',
assignmentLogger: mockLogger,
useExpiredCache: true,
forceReinitialize: true,
});

// Should serve assignment from cache before fetch even fails
Expand All @@ -754,6 +813,7 @@ describe('initialization options', () => {
baseUrl: 'https://thisisabaddomainforthistest.com',
assignmentLogger: mockLogger,
useExpiredCache: true,
forceReinitialize: true,
}),
).rejects.toThrow();
});
Expand Down Expand Up @@ -854,6 +914,7 @@ describe('initialization options', () => {
baseUrl,
assignmentLogger: mockLogger,
updateOnFetch,
forceReinitialize: true,
});

expect(fetchCallCount).toBe(1);
Expand All @@ -871,6 +932,7 @@ describe('initialization options', () => {
assignmentLogger: mockLogger,
updateOnFetch,
useExpiredCache: true,
forceReinitialize: true,
});

// Should serve assignment from cache before fetch completes
Expand Down Expand Up @@ -900,6 +962,7 @@ describe('initialization options', () => {
assignmentLogger: mockLogger,
updateOnFetch,
maxCacheAgeSeconds: fetchResolveDelayMs * 10,
forceReinitialize: true,
});

// No fetch will have been kicked off because of valid cache; previously fetched values will be served
Expand Down Expand Up @@ -942,6 +1005,7 @@ describe('initialization options', () => {
apiKey,
baseUrl,
assignmentLogger: mockLogger,
forceReinitialize: true,
});
});

Expand All @@ -955,6 +1019,7 @@ describe('initialization options', () => {
apiKey,
baseUrl,
assignmentLogger: mockLogger,
forceReinitialize: true,
});
expect(getInstance().getStringAssignment(flagKey, 'subject', {}, 'default-value')).toBe(
'default-value',
Expand Down
22 changes: 21 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ export interface IClientConfig {
* than the default storage provided by the SDK.
*/
persistentStore?: IAsyncStore<Flag>;

/**
* Force reinitialize the SDK if it is already initialized.
*/
forceReinitialize?: boolean;
}

export interface IClientConfigSync {
Expand Down Expand Up @@ -381,8 +386,23 @@ export async function init(config: IClientConfig): Promise<EppoClient> {
validation.validateNotBlank(config.apiKey, 'API key required');
let initializationError: Error | undefined;
const instance = EppoJSClient.instance;
const { apiKey, persistentStore, baseUrl, maxCacheAgeSeconds, updateOnFetch } = config;
const { apiKey, persistentStore, baseUrl, maxCacheAgeSeconds, updateOnFetch, forceReinitialize } =
config;
try {
if (EppoJSClient.initialized) {
if (forceReinitialize) {
applicationLogger.warn(
'Eppo SDK is already initialized, reinitializing since forceReinitialize is true.',
);
EppoJSClient.initialized = false;
} else {
applicationLogger.warn(
'Eppo SDK is already initialized, skipping reinitialization since forceReinitialize is false.',
);
return instance;
}
}

// If any existing instances; ensure they are not polling
instance.stopPolling();
// Set up assignment logger and cache
Expand Down

0 comments on commit c34f847

Please sign in to comment.