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
2 changes: 2 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
- Removed `/mySegments` endpoint from SplitAPI module, as it is replaced by `/memberships` endpoint.
- Removed support for MY_SEGMENTS_UPDATE and MY_SEGMENTS_UPDATE_V2 notification types, as they are replaced by MEMBERSHIPS_MS_UPDATE and MEMBERSHIPS_LS_UPDATE notification types.
- Removed the deprecated `GOOGLE_ANALYTICS_TO_SPLIT` and `SPLIT_TO_GOOGLE_ANALYTICS` integrations.
- Removed the migration logic for the old format of MySegments keys in LocalStorage introduced in JavaScript SDK v10.17.3.
- Removed the `sdkClientMethodCSWithTT` function, which handled the logic to bound an optional traffic type to SDK clients. Client-side SDK implementations must use `sdkClientMethodCS` module, which, unlike the previous function, does not allow passing a traffic type but simplifies the SDK API.
- Removed internal ponyfills for `Map` and `Set` global objects, dropping support for IE and other outdated browsers. The SDK now requires the runtime environment to support these features natively or to provide a polyfill.

1.17.0 (September 6, 2024)
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@splitsoftware/splitio-commons",
"version": "1.17.1-rc.1",
"version": "2.0.0-rc.0",
"description": "Split JavaScript SDK common components",
"main": "cjs/index.js",
"module": "esm/index.js",
Expand Down
69 changes: 23 additions & 46 deletions src/sdkClient/__tests__/sdkClientMethodCS.spec.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { sdkClientMethodCSFactory as sdkClientMethodCSWithTTFactory } from '../sdkClientMethodCSWithTT';
import { sdkClientMethodCSFactory } from '../sdkClientMethodCS';
import { assertClientApi } from './testUtils';
import { telemetryTrackerFactory } from '../../trackers/telemetryTracker';
import { settingsWithKey, settingsWithKeyAndTT, settingsWithKeyObject } from '../../utils/settingsValidation/__tests__/settings.mocks';
import { settingsWithKey, settingsWithKeyObject } from '../../utils/settingsValidation/__tests__/settings.mocks';

const partialStorages: { destroy: jest.Mock }[] = [];

Expand Down Expand Up @@ -75,13 +74,7 @@ describe('sdkClientMethodCSFactory', () => {
params.clients = {};
});

// list of factory functions and their types (whether it ignores TT or not)
const testTargets = [
[sdkClientMethodCSWithTTFactory, false],
[sdkClientMethodCSFactory, true]
];

test.each(testTargets)('main client', (sdkClientMethodCSFactory) => {
test('main client', () => {
// @ts-expect-error
const sdkClientMethod = sdkClientMethodCSFactory(params);

Expand All @@ -106,21 +99,20 @@ describe('sdkClientMethodCSFactory', () => {

});

test.each(testTargets)('multiple clients', async (sdkClientMethodCSFactory, ignoresTT) => {
test('multiple clients', async () => {

// @ts-expect-error
const sdkClientMethod = sdkClientMethodCSFactory(params);

// calling the function with a diferent key than settings, should return a new client instance
// calling the function with a different key than settings, should return a new client instance
const newClients = new Set([
sdkClientMethod('other-key'), // new client
sdkClientMethod('other-key', 'other-tt'), // new client
sdkClientMethod({ matchingKey: 'other-key', bucketingKey: 'buck' }) // new client
sdkClientMethod('other-key'), // @ts-expect-error
sdkClientMethod('other-key', 'ignored-tt'),
sdkClientMethod({ matchingKey: 'other-key', bucketingKey: 'buck' })
]);
if (ignoresTT) expect(newClients.size).toBe(2);
else expect(newClients.size).toBe(3);
expect(newClients.size).toBe(2);

// each new client must follog the Client API
// each new client must follow the Client API
newClients.forEach(newClient => {
assertClientApi(newClient);
expect(newClient).not.toBe(sdkClientMethod());
Expand Down Expand Up @@ -150,7 +142,7 @@ describe('sdkClientMethodCSFactory', () => {

});

test.each(testTargets)('return main client instance if called with same key', (sdkClientMethodCSFactory) => {
test('returns main client instance if called with same key', () => {

params.settings = settingsWithKey;
// @ts-expect-error
Expand All @@ -163,20 +155,7 @@ describe('sdkClientMethodCSFactory', () => {
expect(params.syncManager.shared).not.toBeCalled();
});

test.each(testTargets)('return main client instance if called with same key and TT', (sdkClientMethodCSFactory) => {

params.settings = settingsWithKeyAndTT;
// @ts-expect-error
const sdkClientMethod = sdkClientMethodCSFactory(params);

expect(sdkClientMethod()).toBe(sdkClientMethod(settingsWithKeyAndTT.core.key, settingsWithKeyAndTT.core.trafficType));

expect(params.storage.shared).not.toBeCalled();
expect(params.sdkReadinessManager.shared).not.toBeCalled();
expect(params.syncManager.shared).not.toBeCalled();
});

test.each(testTargets)('return main client instance if called with same key object', (sdkClientMethodCSFactory) => {
test('returns main client instance if called with same key object', () => {
// @ts-expect-error
params.settings = settingsWithKeyObject;
// @ts-expect-error
Expand All @@ -189,39 +168,37 @@ describe('sdkClientMethodCSFactory', () => {
expect(params.syncManager.shared).not.toBeCalled();
});

test.each(testTargets)('return same client instance if called with same key or traffic type (input validation)', (sdkClientMethodCSFactory, ignoresTT) => {
test('returns same client instance if called with same key (input validation)', () => {
// @ts-expect-error
const sdkClientMethod = sdkClientMethodCSFactory(params);

const clientInstance = sdkClientMethod('key', 'tt');
const clientInstance = sdkClientMethod('key');

expect(sdkClientMethod('key', 'tT')).toBe(clientInstance); // No new client created: TT is lowercased / ignored
expect(sdkClientMethod(' key ', 'tt')).toBe(clientInstance); // No new client created: key is trimmed
expect(sdkClientMethod({ matchingKey: 'key ', bucketingKey: ' key' }, 'TT')).toBe(clientInstance); // No new client created: key object is equivalent to 'key' string
expect(sdkClientMethod('key')).toBe(clientInstance); // No new client created: same key
expect(sdkClientMethod(' key ')).toBe(clientInstance); // No new client created: key is trimmed
expect(sdkClientMethod({ matchingKey: 'key ', bucketingKey: ' key' })).toBe(clientInstance); // No new client created: key object is equivalent to 'key' string

expect(params.storage.shared).toBeCalledTimes(1);
expect(params.sdkReadinessManager.shared).toBeCalledTimes(1);
expect(params.syncManager.shared).toBeCalledTimes(1);

expect(sdkClientMethod('KEY', 'tt')).not.toBe(clientInstance); // New client created: key is case-sensitive
if (!ignoresTT) expect(sdkClientMethod('key', 'TT ')).not.toBe(clientInstance); // New client created: TT is not trimmed
expect(sdkClientMethod('KEY')).not.toBe(clientInstance); // New client created: key is case-sensitive

const clientCount = ignoresTT ? 2 : 3;
const clientCount = 2;
expect(params.storage.shared).toBeCalledTimes(clientCount);
expect(params.sdkReadinessManager.shared).toBeCalledTimes(clientCount);
expect(params.syncManager.shared).toBeCalledTimes(clientCount);
});

test.each(testTargets)('invalid calls throw an error', (sdkClientMethodCSFactory, ignoresTT) => {
test('invalid calls throw an error', () => {
// @ts-expect-error
const sdkClientMethod = sdkClientMethodCSFactory(params);
const sdkClientMethod = sdkClientMethodCSFactory(params); // @ts-expect-error
expect(() => sdkClientMethod({ matchingKey: settingsWithKey.core.key, bucketingKey: undefined })).toThrow('Shared Client needs a valid key.');
if (!ignoresTT) expect(() => sdkClientMethod('valid-key', ['invalid-TT'])).toThrow('Shared Client needs a valid traffic type or no traffic type at all.');
});

test.each(testTargets)('attributes binding - main client', (sdkClientMethodCSFactory) => {
test('attributes binding - main client', () => {
// @ts-expect-error
const sdkClientMethod = sdkClientMethodCSFactory(params);
const sdkClientMethod = sdkClientMethodCSFactory(params) as any;

// should return a function
expect(typeof sdkClientMethod).toBe('function');
Expand Down Expand Up @@ -273,7 +250,7 @@ describe('sdkClientMethodCSFactory', () => {
});


test.each(testTargets)('attributes binding - shared clients', (sdkClientMethodCSFactory) => {
test('attributes binding - shared clients', () => {
// @ts-expect-error
const sdkClientMethod = sdkClientMethodCSFactory(params);

Expand Down
13 changes: 5 additions & 8 deletions src/sdkClient/clientCS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,17 @@ import { clientAttributesDecoration } from './clientAttributesDecoration';


/**
* Decorator that binds a key and (optionally) a traffic type to client methods
* Decorator that binds a key to client methods
*
* @param client sync client instance
* @param key validated split key
* @param trafficType validated traffic type
*/
export function clientCSDecorator(log: ILogger, client: SplitIO.IClient, key: SplitIO.SplitKey, trafficType?: string): SplitIO.ICsClient {
export function clientCSDecorator(log: ILogger, client: SplitIO.IClient, key: SplitIO.SplitKey): SplitIO.ICsClient {

let clientCS = clientAttributesDecoration(log, client);

return objectAssign(clientCS, {
// In the client-side API, we bind a key to the client `getTreatment*` methods
// In the client-side API, we bind a key to the client `getTreatment*` and `track` methods
getTreatment: clientCS.getTreatment.bind(clientCS, key),
getTreatmentWithConfig: clientCS.getTreatmentWithConfig.bind(clientCS, key),
getTreatments: clientCS.getTreatments.bind(clientCS, key),
Expand All @@ -26,12 +25,10 @@ export function clientCSDecorator(log: ILogger, client: SplitIO.IClient, key: Sp
getTreatmentsByFlagSet: clientCS.getTreatmentsByFlagSet.bind(clientCS, key),
getTreatmentsWithConfigByFlagSet: clientCS.getTreatmentsWithConfigByFlagSet.bind(clientCS, key),

// Key is bound to the `track` method. Same thing happens with trafficType but only if provided
track: trafficType ? clientCS.track.bind(clientCS, key, trafficType) : clientCS.track.bind(clientCS, key),
track: clientCS.track.bind(clientCS, key),

// Not part of the public API. These properties are used to support other modules (e.g., Split Suite)
isClientSide: true,
key,
trafficType
key
}) as SplitIO.ICsClient;
}
2 changes: 1 addition & 1 deletion src/sdkClient/sdkClientMethodCS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function sdkClientMethodCSFactory(params: ISdkFactoryContext): (key?: Spl
return mainClientInstance;
}

// Validate the key value. The trafficType (2nd argument) is ignored
// Validate the key value
const validKey = validateKey(log, key, LOG_PREFIX_CLIENT_INSTANTIATION);
if (validKey === false) {
throw new Error('Shared Client needs a valid key.');
Expand Down
98 changes: 0 additions & 98 deletions src/sdkClient/sdkClientMethodCSWithTT.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/sdkFactory/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export interface ISdkFactoryParams {

// Sdk client method factory (ISDK::client method).
// It Allows to distinguish SDK clients with the client-side API (`ICsSDK`) or server-side API (`ISDK` or `IAsyncSDK`).
sdkClientMethodFactory: (params: ISdkFactoryContext) => ({ (): SplitIO.ICsClient; (key: SplitIO.SplitKey, trafficType?: string | undefined): SplitIO.ICsClient; } | (() => SplitIO.IClient) | (() => SplitIO.IAsyncClient))
sdkClientMethodFactory: (params: ISdkFactoryContext) => ({ (): SplitIO.ICsClient; (key: SplitIO.SplitKey): SplitIO.ICsClient; } | (() => SplitIO.IClient) | (() => SplitIO.IAsyncClient))

// Impression observer factory.
impressionsObserverFactory: () => IImpressionObserver
Expand Down
13 changes: 0 additions & 13 deletions src/storages/KeyBuilderCS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { KeyBuilder } from './KeyBuilder';
export interface MySegmentsKeyBuilder {
buildSegmentNameKey(segmentName: string): string;
extractSegmentName(builtSegmentKeyName: string): string | undefined;
extractOldSegmentKey(builtSegmentKeyName: string): string | undefined;
buildTillKey(): string;
}

Expand Down Expand Up @@ -33,14 +32,6 @@ export class KeyBuilderCS extends KeyBuilder implements MySegmentsKeyBuilder {
return builtSegmentKeyName.substr(prefix.length);
}

// @BREAKING: The key used to start with the matching key instead of the prefix, this was changed on version 10.17.3
extractOldSegmentKey(builtSegmentKeyName: string) {
const prefix = `${this.matchingKey}.${this.prefix}.segment.`;

if (startsWith(builtSegmentKeyName, prefix))
return builtSegmentKeyName.substr(prefix.length);
}

buildLastUpdatedKey() {
return `${this.prefix}.splits.lastUpdated`;
}
Expand All @@ -66,10 +57,6 @@ export function myLargeSegmentsKeyBuilder(prefix: string, matchingKey: string):
if (startsWith(builtSegmentKeyName, p)) return builtSegmentKeyName.substr(p.length);
},

extractOldSegmentKey() {
return undefined;
},

buildTillKey() {
return `${prefix}.${matchingKey}.largeSegments.till`;
}
Expand Down
22 changes: 1 addition & 21 deletions src/storages/inLocalStorage/MySegmentsCacheInLocal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,27 +51,7 @@ export class MySegmentsCacheInLocal extends AbstractSegmentsCacheSync {
return Object.keys(localStorage).reduce((accum, key) => {
let segmentName = this.keys.extractSegmentName(key);

if (segmentName) {
accum.push(segmentName);
} else {
// @TODO @BREAKING: This is only to clean up "old" keys. Remove this whole else code block
segmentName = this.keys.extractOldSegmentKey(key);

if (segmentName) { // this was an old segment key, let's clean up.
const newSegmentKey = this.keys.buildSegmentNameKey(segmentName);
try {
// If the new format key is not there, create it.
if (!localStorage.getItem(newSegmentKey)) {
localStorage.setItem(newSegmentKey, DEFINED);
// we are migrating a segment, let's track it.
accum.push(segmentName);
}
localStorage.removeItem(key); // we migrated the current key, let's delete it.
} catch (e) {
this.log.error(e);
}
}
}
if (segmentName) accum.push(segmentName);

return accum;
}, [] as string[]);
Expand Down
Loading