Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Breaking change] Upgrade JS SDK v11 #125

Merged
merged 4 commits into from
Nov 12, 2024
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
6 changes: 5 additions & 1 deletion CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
2.0.0 (September XX, 2024)
2.0.0 (November XX, 2024)
- Added support for targeting rules based on large segments.
- Updated @splitsoftware/splitio package to version 11.0.1 that includes major updates, and updated some transitive dependencies for vulnerability fixes.
- Renamed distribution folders from `/lib` to `/cjs` for CommonJS build, and `/es` to `/esm` for ECMAScript Modules build.
- BREAKING CHANGES:
- Removed the `core.trafficType` configuration option and made required the `trafficType` argument when calling the `track` helper function. This is because traffic types can no longer be bound to SDK clients in JavaScript SDK v11.0.0, and so the traffic type must be provided as argument in the `track` method calls.

1.14.1 (October 15, 2024)
- Bugfixing - Fixed error in `splitReducer` when handling actions with a `null` payload, preventing crashes caused by accessing undefined payload properties (Related to https://github.com/splitio/redux-client/issues/121).
Expand Down
49 changes: 18 additions & 31 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"check": "npm run check:lint && npm run check:types",
"check:lint": "eslint 'src/**/*.ts'",
"check:types": "tsc --noEmit",
"test": "jest src",
"test": "jest src --silent",
"test:watch": "npm test -- --watch",
"test:coverage": "jest src --coverage",
"test:debug": "node --inspect node_modules/.bin/jest --runInBand",
Expand Down Expand Up @@ -59,7 +59,7 @@
},
"homepage": "https://github.com/splitio/redux-client#readme",
"dependencies": {
"@splitsoftware/splitio": "10.28.1-rc.2",
"@splitsoftware/splitio": "11.0.1",
"tslib": "^2.3.1"
},
"devDependencies": {
Expand Down
33 changes: 5 additions & 28 deletions src/__tests__/helpers.browser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,54 +122,31 @@ describe('track', () => {

it('logs error and returns false if the SDK was not initialized', () => {
const errorSpy = jest.spyOn(console, 'error');
expect(track({ eventType: 'event' })).toBe(false);
expect(track({ eventType: 'event', trafficType: 'user' })).toBe(false);
expect(errorSpy).toBeCalledWith(ERROR_TRACK_NO_INITSPLITSDK);
});

it('should invoke the track method of the main client (no traffic type in config)', () => {
it('should invoke the track method of the main client', () => {
initSplitSdk({ config: sdkBrowserConfig });

expect(track({ eventType: 'event', trafficType: 'user' })).toBe(true);
expect((splitSdk.factory as any).client().track.mock.calls.length).toBe(1);
expect((splitSdk.factory as any).client().track.mock.calls[0][0]).toBe('user');
expect((splitSdk.factory as any).client().track.mock.calls[0][1]).toBe('event');

// TT must be provided if not included in the config
// @ts-expect-error TT must be provided
expect(track({ eventType: 'event' })).toBe(false);
});

it('should invoke the track method of the main client (traffic type in config)', () => {
initSplitSdk({ config: { ...sdkBrowserConfig, core: { ...sdkBrowserConfig.core, trafficType: 'user' } } });

expect(track({ eventType: 'event' })).toBe(true);
expect((splitSdk.factory as any).client().track.mock.calls.length).toBe(1);
expect((splitSdk.factory as any).client().track.mock.calls[0][0]).toBe('event');

// TT is ignored if included in the config
expect(track({ eventType: 'event', trafficType: 'user' })).toBe(true);
});

it('should invoke the track method of a shared client (no traffic type in config)', () => {
it('should invoke the track method of a shared client', () => {
initSplitSdk({ config: sdkBrowserConfig });

expect(track({ eventType: 'event', key: 'user1', trafficType: 'user' })).toBe(true);
expect((splitSdk.factory as any).client('user1').track.mock.calls.length).toBe(1);
expect((splitSdk.factory as any).client('user1').track.mock.calls[0][0]).toBe('user');
expect((splitSdk.factory as any).client('user1').track.mock.calls[0][1]).toBe('event');

// TT must be provided if key is provided
expect(track({ eventType: 'event', key: 'user1' })).toBe(false);
});

it('should invoke the track method of a shared client (traffic type in config)', () => {
initSplitSdk({ config: { ...sdkBrowserConfig, core: { ...sdkBrowserConfig.core, trafficType: 'user' } } });

expect(track({ eventType: 'event', key: 'user1', trafficType: 'user' })).toBe(true);
expect((splitSdk.factory as any).client('user1').track.mock.calls.length).toBe(1);
expect((splitSdk.factory as any).client('user1').track.mock.calls[0][0]).toBe('user');
expect((splitSdk.factory as any).client('user1').track.mock.calls[0][1]).toBe('event');

// TT must be provided if key is provided, no matter if present in the config, since that TT is for main client
// @ts-expect-error TT must be provided
expect(track({ eventType: 'event', key: 'user1' })).toBe(false);
});

Expand Down
23 changes: 10 additions & 13 deletions src/__tests__/utils/mockBrowserSplitSdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function parseKey(key: SplitIO.SplitKey): SplitIO.SplitKey {
};
}
}
function buildInstanceId(key: any, trafficType: string | undefined) {
function buildInstanceId(key: any, trafficType?: string) {
return `${key.matchingKey ? key.matchingKey : key}-${key.bucketingKey ? key.bucketingKey : key}-${trafficType !== undefined ? trafficType : ''}`;
}

Expand All @@ -33,7 +33,7 @@ export function mockSdk() {
// ATM, isReadyFromCache is shared among clients
let isReadyFromCache = false;

function mockClient(key?: SplitIO.SplitKey) {
function mockClient(_key?: SplitIO.SplitKey) {
// Readiness
let isReady = false;
let hasTimedout = false;
Expand All @@ -51,14 +51,10 @@ export function mockSdk() {
__emitter__.once(Event.SDK_READY_TIMED_OUT, () => { hasTimedout = true; syncLastUpdate(); });
__emitter__.on(Event.SDK_UPDATE, () => { syncLastUpdate(); });

let attributesCache = {};

// Client methods
const track: jest.Mock = jest.fn((tt, et, v, p) => {
if (!(key || !config.core.trafficType)) {
p = v;
v = et;
et = tt;
tt = config.core.trafficType;
}
return typeof tt === 'string' &&
typeof et === 'string' &&
(typeof v === 'number' || typeof v === 'undefined') &&
Expand All @@ -76,14 +72,16 @@ export function mockSdk() {
return acc;
}, {});
});
const setAttributes: jest.Mock = jest.fn(() => {
const setAttributes: jest.Mock = jest.fn((attributes) => {
attributesCache = Object.assign(attributesCache, attributes);
return true;
});
const clearAttributes: jest.Mock = jest.fn(() => {
attributesCache = {};
return true;
});
const getAttributes: jest.Mock = jest.fn(() => {
return true;
return attributesCache;
});
const ready: jest.Mock = jest.fn(() => {
return promiseWrapper(new Promise<void>((res, rej) => {
Expand Down Expand Up @@ -130,11 +128,10 @@ export function mockSdk() {
const manager: jest.Mock = jest.fn().mockReturnValue({ names, split, splits });

// Cache of clients
const __clients__: { [key: string]: any } = {};
const __clients__: { [instanceId: string]: any } = {};
const client = jest.fn((key?: SplitIO.SplitKey) => {
const clientKey = key || parseKey(config.core.key);
const clientTT = key ? undefined : config.core.trafficType;
const instanceId = buildInstanceId(clientKey, clientTT);
const instanceId = buildInstanceId(clientKey);
return __clients__[instanceId] || (__clients__[instanceId] = mockClient(key));
});

Expand Down
8 changes: 6 additions & 2 deletions src/__tests__/utils/mockNodeSplitSdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,12 @@ function mockClient() {
__emitter__.on(Event.SDK_UPDATE, () => { syncLastUpdate(); });

// Client methods
const track: jest.Mock = jest.fn(() => {
return true;
const track: jest.Mock = jest.fn((key, tt, et, v, p) => {
return typeof key === 'string' &&
typeof tt === 'string' &&
typeof et === 'string' &&
(typeof v === 'number' || typeof v === 'undefined') &&
(typeof p === 'object' || typeof p === 'undefined');
});
const getTreatmentsWithConfig: jest.Mock = jest.fn((key, featureFlagNames) => {
return featureFlagNames.reduce((acc: SplitIO.TreatmentsWithConfig, featureFlagName: string) => {
Expand Down
8 changes: 4 additions & 4 deletions src/asyncActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import { matching, __getStatus, validateGetTreatmentsParams, isMainClient } from
export interface ISplitSdk {
config: SplitIO.IBrowserSettings | SplitIO.INodeSettings;
splitio: ISplitFactoryBuilder;
factory: SplitIO.ISDK;
sharedClients: { [stringKey: string]: SplitIO.IClient };
factory: SplitIO.IBrowserSDK | SplitIO.ISDK;
sharedClients: { [stringKey: string]: SplitIO.IBrowserClient };
isDetached: boolean; // true: server-side, false: client-side (i.e., client with bound key)
dispatch: Dispatch<Action>;
}
Expand Down Expand Up @@ -173,7 +173,7 @@ export function getTreatments(params: IGetTreatmentsParams): Action | (() => voi
} else { // Split SDK running in Node

// Evaluate Split and return redux action.
const client = splitSdk.factory.client();
const client = splitSdk.factory.client() as SplitIO.IClient;
const treatments = splitNames ?
client.getTreatmentsWithConfig(params.key, splitNames, params.attributes) :
client.getTreatmentsWithConfigByFlagSets(params.key, flagSets, params.attributes);
Expand All @@ -185,7 +185,7 @@ export function getTreatments(params: IGetTreatmentsParams): Action | (() => voi
/**
* Interface of SDK client for not detached execution (browser).
*/
interface IClientNotDetached extends SplitIO.IClient {
interface IClientNotDetached extends SplitIO.IBrowserClient {
_trackingStatus?: boolean;
/**
* stored evaluations to execute on SDK update. It is an object because we might
Expand Down
18 changes: 7 additions & 11 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,20 @@ export function track(params: ITrackParams): boolean {
console.error(ERROR_TRACK_NO_INITSPLITSDK);
return false;
}
const trackParams = [params.eventType, params.value, params.properties];
let client; // Client getting variates depending on browser or node.

const { key, trafficType, eventType, value, properties } = params;

if (splitSdk.isDetached) { // Node
// In node, user must always provide key and TT as params
client = splitSdk.factory.client();
trackParams.unshift(params.key, params.trafficType);
const client = splitSdk.factory.client() as SplitIO.IClient;

return client.track(key, trafficType, eventType, value, properties);
} else { // Browser
// client is a shared or main client whether or not the key is provided
client = getClient(splitSdk, params.key);
const client = getClient(splitSdk, params.key);

// TT is required if the key is provided (shared client) or if not present in config (main client)
if (params.key || !(splitSdk.config.core as SplitIO.IBrowserSettings['core']).trafficType) {
trackParams.unshift(params.trafficType);
}
return client.track(trafficType, eventType, value, properties);
}

return client.track(...trackParams as [string, any]);
}

/**
Expand Down
7 changes: 3 additions & 4 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,9 @@ export interface ITrackParams {
key?: SplitIO.SplitKey;

/**
* the traffic type of the key in the track call. If not provided, it defaults to the traffic type defined in the SDK
* config object. If not provided either in the SDK setting, the function logs an error message and returns false.
* the traffic type of the key in the track call. If not provided, the function logs an error message and returns false.
*/
trafficType?: string;
trafficType: string;

/**
* The event type that this event should correspond to. The expected data type is String.
Expand All @@ -194,7 +193,7 @@ export interface ITrackParams {
properties?: SplitIO.Properties;
}

export type ISplitFactoryBuilder = (settings: SplitIO.IBrowserSettings | SplitIO.INodeSettings) => SplitIO.ISDK;
export type ISplitFactoryBuilder = ((settings: SplitIO.IBrowserSettings) => SplitIO.IBrowserSDK) | ((settings: SplitIO.INodeSettings) => SplitIO.ISDK);

export type ISplitAction = {
type: string;
Expand Down
2 changes: 1 addition & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export interface IClientStatus {
}

// The following util might be removed in the future, if the JS SDK extends its public API with a "getStatus" method
export function __getStatus(client: SplitIO.IClient): IClientStatus {
export function __getStatus(client: SplitIO.IBasicClient): IClientStatus {
// @ts-expect-error, function exists but it is not part of JS SDK type definitions
return client.__getStatus();
}
Expand Down
Loading