Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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