Skip to content

Commit

Permalink
Merge pull request #199 from splitio/release_v1.13.0
Browse files Browse the repository at this point in the history
Upgrade JS SDK dependency and logic for handling `lastUpdate` property
  • Loading branch information
EmilianoSanchez authored Sep 6, 2024
2 parents 49ff142 + 94825dd commit eeffae2
Show file tree
Hide file tree
Showing 12 changed files with 412 additions and 498 deletions.
5 changes: 5 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
1.13.0 (September 6, 2024)
- Updated @splitsoftware/splitio package to version 10.28.0 that includes minor updates:
- Added `sync.requestOptions.getHeaderOverrides` configuration option to enhance SDK HTTP request Headers for Authorization Frameworks.
- Updated some transitive dependencies for vulnerability fixes.

1.12.1 (August 2, 2024)
- Updated @splitsoftware/splitio package to version 10.27.0 and some transitive dependencies for vulnerability fixes.

Expand Down
797 changes: 353 additions & 444 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@splitsoftware/splitio-react",
"version": "1.12.1",
"version": "1.13.0",
"description": "A React library to easily integrate and use Split JS SDK",
"main": "lib/index.js",
"module": "es/index.js",
Expand Down Expand Up @@ -63,7 +63,7 @@
},
"homepage": "https://github.com/splitio/react-client#readme",
"dependencies": {
"@splitsoftware/splitio": "10.27.0",
"@splitsoftware/splitio": "10.28.0",
"memoize-one": "^5.1.1",
"shallowequal": "^1.1.0",
"tslib": "^2.3.1"
Expand Down
2 changes: 1 addition & 1 deletion src/SplitClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export class SplitComponent extends React.Component<IUpdateProps & { factory: Sp
}

update = () => {
this.setState({ lastUpdate: (this.state.client as IClientWithContext).lastUpdate });
this.setState({ lastUpdate: (this.state.client as IClientWithContext).__getStatus().lastUpdate });
}

componentDidMount() {
Expand Down
5 changes: 3 additions & 2 deletions src/__tests__/SplitClient.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { SplitFactoryProvider } from '../SplitFactoryProvider';
import { SplitClient } from '../SplitClient';
import { SplitContext } from '../SplitContext';
import { testAttributesBinding, TestComponentProps } from './testUtils/utils';
import { IClientWithContext } from '../utils';

describe('SplitClient', () => {

Expand Down Expand Up @@ -55,7 +56,7 @@ describe('SplitClient', () => {
expect(hasTimedout).toBe(false);
expect(isTimedout).toBe(false);
expect(isDestroyed).toBe(false);
expect(lastUpdate).toBe(0);
expect(lastUpdate).toBe((outerFactory.client() as IClientWithContext).__getStatus().lastUpdate);

return null;
}}
Expand Down Expand Up @@ -212,7 +213,7 @@ describe('SplitClient', () => {
count++;

// side effect in the render phase
if (!(client as any).__getStatus().isReady) {
if (!(client as IClientWithContext).__getStatus().isReady) {
console.log('emit');
(client as any).__emitter__.emit(Event.SDK_READY);
}
Expand Down
4 changes: 2 additions & 2 deletions src/__tests__/SplitFactory.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { ISplitFactoryChildProps } from '../types';
import { SplitFactory } from '../SplitFactory';
import { SplitClient } from '../SplitClient';
import { SplitContext } from '../SplitContext';
import { __factories } from '../utils';
import { __factories, IClientWithContext } from '../utils';
import { WARN_SF_CONFIG_AND_FACTORY, ERROR_SF_NO_CONFIG_AND_FACTORY } from '../constants';

describe('SplitFactory', () => {
Expand Down Expand Up @@ -54,7 +54,7 @@ describe('SplitFactory', () => {
expect(hasTimedout).toBe(false);
expect(isTimedout).toBe(false);
expect(isDestroyed).toBe(false);
expect(lastUpdate).toBe(0);
expect(lastUpdate).toBe((outerFactory.client() as IClientWithContext).__getStatus().lastUpdate);
expect((factory as SplitIO.ISDK).settings.version).toBe(outerFactory.settings.version);
return null;
}}
Expand Down
4 changes: 2 additions & 2 deletions src/__tests__/SplitFactoryProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { ISplitFactoryChildProps } from '../types';
import { SplitFactoryProvider } from '../SplitFactoryProvider';
import { SplitClient } from '../SplitClient';
import { SplitContext } from '../SplitContext';
import { __factories } from '../utils';
import { __factories, IClientWithContext } from '../utils';
import { WARN_SF_CONFIG_AND_FACTORY } from '../constants';

describe('SplitFactoryProvider', () => {
Expand Down Expand Up @@ -54,7 +54,7 @@ describe('SplitFactoryProvider', () => {
expect(hasTimedout).toBe(false);
expect(isTimedout).toBe(false);
expect(isDestroyed).toBe(false);
expect(lastUpdate).toBe(0);
expect(lastUpdate).toBe((outerFactory.client() as IClientWithContext).__getStatus().lastUpdate);
expect((factory as SplitIO.ISDK).settings.version).toBe(outerFactory.settings.version);
return null;
}}
Expand Down
4 changes: 2 additions & 2 deletions src/__tests__/SplitTreatments.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jest.mock('@splitsoftware/splitio/client', () => {
});
import { SplitFactory as SplitSdk } from '@splitsoftware/splitio/client';
import { sdkBrowser } from './testUtils/sdkConfigs';
import { getStatus } from '../utils';
import { getStatus, IClientWithContext } from '../utils';
import { newSplitFactoryLocalhostInstance } from './testUtils/utils';
import { CONTROL_WITH_CONFIG } from '../constants';

Expand Down Expand Up @@ -70,7 +70,7 @@ describe('SplitTreatments', () => {
expect(clientMock.getTreatmentsWithConfig.mock.calls.length).toBe(1);
expect(treatments).toBe(clientMock.getTreatmentsWithConfig.mock.results[0].value);
expect(featureFlagNames).toBe(clientMock.getTreatmentsWithConfig.mock.calls[0][0]);
expect([isReady2, isReadyFromCache, hasTimedout, isTimedout, isDestroyed, lastUpdate]).toStrictEqual([true, false, false, false, false, 0]);
expect([isReady2, isReadyFromCache, hasTimedout, isTimedout, isDestroyed, lastUpdate]).toStrictEqual([true, false, false, false, false, (outerFactory.client() as IClientWithContext).__getStatus().lastUpdate]);
return null;
}}
</SplitTreatments>
Expand Down
46 changes: 29 additions & 17 deletions src/__tests__/testUtils/mockSplitSdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export const reactSdkVersion = `react-${reactSdkPackageJson.version}`;
export const Event = {
SDK_READY_TIMED_OUT: 'init::timeout',
SDK_READY: 'init::ready',
SDK_UPDATE: 'state::update',
SDK_READY_FROM_CACHE: 'init::cache-ready',
SDK_UPDATE: 'state::update',
};

function parseKey(key: SplitIO.SplitKey): SplitIO.SplitKey {
Expand All @@ -32,14 +32,22 @@ function buildInstanceId(key: any, trafficType: string | undefined) {

function mockClient(_key: SplitIO.SplitKey, _trafficType?: string) {
// Readiness
let __isReady__: boolean | undefined;
let __isReadyFromCache__: boolean | undefined;
let __hasTimedout__: boolean | undefined;
let __isDestroyed__: boolean | undefined;
let isReady = false;
let isReadyFromCache = false;
let hasTimedout = false;
let isDestroyed = false;
let lastUpdate = 0;

function syncLastUpdate() {
const dateNow = Date.now();
lastUpdate = dateNow > lastUpdate ? dateNow : lastUpdate + 1;
}

const __emitter__ = new EventEmitter();
__emitter__.on(Event.SDK_READY, () => { __isReady__ = true; });
__emitter__.on(Event.SDK_READY_FROM_CACHE, () => { __isReadyFromCache__ = true; });
__emitter__.on(Event.SDK_READY_TIMED_OUT, () => { __hasTimedout__ = true; });
__emitter__.on(Event.SDK_READY, () => { isReady = true; syncLastUpdate(); });
__emitter__.on(Event.SDK_READY_FROM_CACHE, () => { isReadyFromCache = true; syncLastUpdate(); });
__emitter__.on(Event.SDK_READY_TIMED_OUT, () => { hasTimedout = true; syncLastUpdate(); });
__emitter__.on(Event.SDK_UPDATE, () => { syncLastUpdate(); });

let attributesCache = {};

Expand Down Expand Up @@ -72,21 +80,24 @@ function mockClient(_key: SplitIO.SplitKey, _trafficType?: string) {
});
const ready: jest.Mock = jest.fn(() => {
return new Promise<void>((res, rej) => {
if (__isReady__) res();
if (isReady) res();
else { __emitter__.on(Event.SDK_READY, res); }
if (__hasTimedout__) rej();
if (hasTimedout) rej();
else { __emitter__.on(Event.SDK_READY_TIMED_OUT, rej); }
});
});
const __getStatus = () => ({
isReady: __isReady__ || false,
isReadyFromCache: __isReadyFromCache__ || false,
hasTimedout: __hasTimedout__ || false,
isDestroyed: __isDestroyed__ || false,
isOperational: ((__isReady__ || __isReadyFromCache__) && !__isDestroyed__) || false,
isReady,
isReadyFromCache,
isTimedout: hasTimedout && !isReady,
hasTimedout,
isDestroyed,
isOperational: (isReady || isReadyFromCache) && !isDestroyed,
lastUpdate,
});
const destroy: jest.Mock = jest.fn(() => {
__isDestroyed__ = true;
isDestroyed = true;
syncLastUpdate();
// __emitter__.removeAllListeners();
return Promise.resolve();
});
Expand All @@ -108,7 +119,8 @@ function mockClient(_key: SplitIO.SplitKey, _trafficType?: string) {
// Restore the mock client to its initial NO-READY status.
// Useful when you want to reuse the same mock between tests after emitting events or destroying the instance.
__restore() {
__isReady__ = __isReadyFromCache__ = __hasTimedout__ = __isDestroyed__ = undefined;
isReady = isReadyFromCache = hasTimedout = isDestroyed = false;
lastUpdate = 0;
}
});
}
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface ISplitStatus {

/**
* isTimedout indicates if the Split SDK client has triggered an SDK_READY_TIMED_OUT event and is not ready to be consumed.
* In other words, `isTimedout` is equivalent to `hasTimeout && !isReady`.
*/
isTimedout: boolean;

Expand Down
2 changes: 1 addition & 1 deletion src/useSplitClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export function useSplitClient(options?: IUseSplitClientOptions): ISplitContextV
React.useEffect(() => {
if (!client) return;

const update = () => setLastUpdate(client.lastUpdate);
const update = () => setLastUpdate(client.__getStatus().lastUpdate);

// Clients are created on the hook's call, so the status may have changed
const statusOnEffect = getStatus(client);
Expand Down
36 changes: 11 additions & 25 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ export interface IClientWithContext extends SplitIO.IBrowserClient {
__getStatus(): {
isReady: boolean;
isReadyFromCache: boolean;
isOperational: boolean;
isTimedout: boolean;
hasTimedout: boolean;
isDestroyed: boolean;
isOperational: boolean;
lastUpdate: number;
};
lastUpdate: number;
}

/**
Expand Down Expand Up @@ -51,23 +52,9 @@ export function getSplitClient(factory: SplitIO.IBrowserSDK, key?: SplitIO.Split
// factory.client is an idempotent operation
const client = (key !== undefined ? factory.client(key, trafficType) : factory.client()) as IClientWithContext;

// Handle client lastUpdate
if (client.lastUpdate === undefined) {
// Remove EventEmitter warning emitted when using multiple SDK hooks or components.
// Unlike JS SDK, users can avoid using the client directly, making the warning irrelevant.
client.setMaxListeners(0);

const updateLastUpdate = () => {
const lastUpdate = Date.now();
client.lastUpdate = lastUpdate > client.lastUpdate ? lastUpdate : client.lastUpdate + 1;
}

client.lastUpdate = 0;
client.on(client.Event.SDK_READY, updateLastUpdate);
client.on(client.Event.SDK_READY_FROM_CACHE, updateLastUpdate);
client.on(client.Event.SDK_READY_TIMED_OUT, updateLastUpdate);
client.on(client.Event.SDK_UPDATE, updateLastUpdate);
}
// Remove EventEmitter warning emitted when using multiple SDK hooks or components.
// Unlike JS SDK, users don't need to access the client directly, making the warning irrelevant.
client.setMaxListeners(0);

if ((factory as IFactoryWithClients).clientInstances) {
(factory as IFactoryWithClients).clientInstances.add(client);
Expand All @@ -89,15 +76,14 @@ export function destroySplitFactory(factory: IFactoryWithClients): Promise<void[
// It might be removed in the future, if the JS SDK extends its public API with a `getStatus` method
export function getStatus(client: SplitIO.IBrowserClient | null): ISplitStatus {
const status = client && (client as IClientWithContext).__getStatus();
const isReady = status ? status.isReady : false;
const hasTimedout = status ? status.hasTimedout : false;

return {
isReady,
isReady: status ? status.isReady : false,
isReadyFromCache: status ? status.isReadyFromCache : false,
isTimedout: hasTimedout && !isReady,
hasTimedout,
isTimedout: status ? status.isTimedout : false,
hasTimedout: status ? status.hasTimedout : false,
isDestroyed: status ? status.isDestroyed : false,
lastUpdate: client ? (client as IClientWithContext).lastUpdate || 0 : 0,
lastUpdate: status ? status.lastUpdate : 0,
};
}

Expand Down

0 comments on commit eeffae2

Please sign in to comment.