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 @@
11.9.0 (November 26, 2025)
- Updated @splitsoftware/splitio-commons package to version 2.9.0, which:
- updates the SDK’s initial synchronization in Node.js (server-side) to use the `startup.requestTimeoutBeforeReady` and `startup.retriesOnFailureBeforeReady` options to control the timeout and retry behavior of segment requests.
- updates the order of storage operations to prevent inconsistent states when using the `LOCALSTORAGE` storage type and the browser’s `localStorage` fails due to quota limits.

11.8.0 (October 30, 2025)
- Added new configuration for Fallback Treatments, which allows setting a treatment value and optional config to be returned in place of "control", either globally or by flag. Read more in our docs.
- Added `client.getStatus()` method to retrieve the client readiness status properties (`isReady`, `isReadyFromCache`, etc).
Expand Down
70 changes: 37 additions & 33 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
@@ -1,6 +1,6 @@
{
"name": "@splitsoftware/splitio",
"version": "11.8.0",
"version": "11.9.0",
"description": "Split SDK",
"files": [
"README.md",
Expand Down Expand Up @@ -38,7 +38,7 @@
"node": ">=14.0.0"
},
"dependencies": {
"@splitsoftware/splitio-commons": "2.8.0",
"@splitsoftware/splitio-commons": "2.9.0",
"bloom-filters": "^3.0.4",
"ioredis": "^4.28.0",
"js-yaml": "^3.13.1",
Expand Down
26 changes: 21 additions & 5 deletions src/__tests__/browserSuites/ready-from-cache.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,18 +108,34 @@ export const expectedHashWithFilter = '2ce5cc38'; // for SDK key '<fake-token-rf

export default function (fetchMock, assert) {

assert.test(t => { // Testing when we start from scratch
assert.test(t => { // Testing when we start from scratch, with an initial localStorage write operation fail (should retry splitChanges with -1)
const testUrls = {
sdk: 'https://sdk.baseurl/readyFromCacheEmpty',
events: 'https://events.baseurl/readyFromCacheEmpty'
};
localStorage.clear();

// simulate a localStorage failure when saving a FF and a membership
const originalSetItem = localStorage.setItem;
localStorage.setItem = (key, value) => {
if (key.includes('.nicolas@split.io.')) {
throw new Error('localStorage.setItem failed');
}
if (key.includes('.split.')) {
localStorage.setItem = originalSetItem;
throw new Error('localStorage.setItem failed');
}
return originalSetItem.call(localStorage, key, value);
};

t.plan(4);

fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', { status: 200, body: splitChangesMock1 });
fetchMock.get(testUrls.sdk + '/memberships/nicolas%40split.io', { status: 200, body: membershipsNicolas });
fetchMock.get(testUrls.sdk + '/memberships/nicolas2%40split.io', { status: 200, body: { 'ms': {} } });
fetchMock.get(testUrls.sdk + '/memberships/nicolas3%40split.io', { status: 200, body: { 'ms': {} } });
fetchMock.getOnce(testUrls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', { status: 200, body: splitChangesMock1 });
fetchMock.getOnce(testUrls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', { status: 200, body: splitChangesMock1 }); // retry
fetchMock.getOnce(testUrls.sdk + '/memberships/nicolas%40split.io', { status: 200, body: membershipsNicolas });
fetchMock.getOnce(testUrls.sdk + '/memberships/nicolas%40split.io', { status: 200, body: membershipsNicolas }); // retry
fetchMock.getOnce(testUrls.sdk + '/memberships/nicolas2%40split.io', { status: 200, body: { 'ms': {} } });
fetchMock.getOnce(testUrls.sdk + '/memberships/nicolas3%40split.io', { status: 200, body: { 'ms': {} } });

const splitio = SplitFactory({
...baseConfig,
Expand Down
11 changes: 9 additions & 2 deletions src/__tests__/consumer/node_redis.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,14 @@ tape('Node.js Redis', function (t) {
t.test('Connection error', assert => {
initializeRedisServer()
.then((server) => {
const sdk = SplitFactory(config);
const sdk = SplitFactory({
...config,
fallbackTreatments: {
byFlag: {
'always-on': 'control_always_on'
}
}
});
const client = sdk.client();

client.once(client.Event.SDK_READY_TIMED_OUT, assert.fail);
Expand Down Expand Up @@ -585,7 +592,7 @@ tape('Node.js Redis', function (t) {
assert.equal(await client.getTreatment('UT_Segment_member', 'UT_NOT_SET_MATCHER', {
permissions: ['not_matching']
}), 'control', 'In the event of a Redis error like a disconnection, getTreatments should not hang but resolve to "control".');
assert.equal(await client.getTreatment('UT_Segment_member', 'always-on'), 'control', 'In the event of a Redis error like a disconnection, getTreatments should not hang but resolve to "control".');
assert.equal(await client.getTreatment('UT_Segment_member', 'always-on'), 'control_always_on', 'In the event of a Redis error like a disconnection, getTreatments should not hang but resolve to fallback treatment or "control".');

assert.false(await client.track('nicolas@split.io', 'user', 'test.redis.event', 18), 'In the event of a Redis error like a disconnection, track should resolve to false.');

Expand Down
6 changes: 5 additions & 1 deletion src/__tests__/nodeSuites/ready-promise.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,11 @@ export default function readyPromiseAssertions(key, fetchMock, assert) {
// /splitChanges takes longer than 'requestTimeoutBeforeReady' only for the first attempt
fetchMock.getOnce(config.urls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', splitChangesMock1, { delay: fromSecondsToMillis(config.startup.requestTimeoutBeforeReady) + 20 });
fetchMock.getOnce(config.urls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', splitChangesMock1, { delay: fromSecondsToMillis(config.startup.requestTimeoutBeforeReady) - 20 });
mockSegmentChanges(fetchMock, new RegExp(config.urls.sdk + '/segmentChanges/*'), ['some_key']);
mockSegmentChanges(fetchMock, new RegExp(config.urls.sdk + '/segmentChanges/(splitters|developers|segment_excluded_by_rbs)'), ['some_key']);
fetchMock.getOnce(config.urls.sdk + '/segmentChanges/employees?since=-1', { status: 500, body: 'server error' });
fetchMock.getOnce(config.urls.sdk + '/segmentChanges/employees?since=-1', { since: -1, till: 10, name: 'employees', added: ['some_key'], removed: [] });
fetchMock.getOnce(config.urls.sdk + '/segmentChanges/employees?since=10', { since: 10, till: 10, name: 'employees', added: [], removed: [] });

fetchMock.postOnce(config.urls.events + '/testImpressions/bulk', 200);
fetchMock.postOnce(config.urls.events + '/testImpressions/count', 200);

Expand Down
2 changes: 1 addition & 1 deletion src/settings/defaults/version.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export const packageVersion = '11.8.0';
export const packageVersion = '11.9.0';
7 changes: 6 additions & 1 deletion ts-tests/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ promise = client.destroy();
promise = SDK.destroy();
// @TODO not public yet
// promise = client.flush();
const promiseWhenReadyFromCache: Promise<boolean> = client.whenReadyFromCache();
let promiseWhenReadyFromCache: Promise<boolean> = client.whenReadyFromCache();

// Get readiness status
let status: SplitIO.ReadinessStatus = client.getStatus();
Expand Down Expand Up @@ -378,10 +378,15 @@ nodeEventEmitter = asyncClient;

// Ready, destroy and flush (same as for sync client, just for interface checking)
promise = asyncClient.ready();
promise = asyncClient.whenReady();
promise = asyncClient.destroy();
promise = AsyncSDK.destroy();
// @TODO not public yet
// promise = asyncClient.flush();
promiseWhenReadyFromCache = asyncClient.whenReadyFromCache();

// Get readiness status
status = asyncClient.getStatus();

// We can call getTreatment but always with a key.
asyncTreatment = asyncClient.getTreatment(splitKey, 'mySplit');
Expand Down