Skip to content

Commit 3c7dedc

Browse files
Merge pull request #430 from splitio/development
Release v2.6.0
2 parents 2934838 + db45108 commit 3c7dedc

28 files changed

+724
-415
lines changed

CHANGES.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
2.6.0 (September 18, 2025)
2+
- Added `storage.wrapper` configuration option to allow the SDK to use a custom storage wrapper for the storage type `LOCALSTORAGE`. Default value is `window.localStorage`.
3+
14
2.5.0 (September 10, 2025)
25
- Added `factory.getRolloutPlan()` method for standalone server-side SDKs, which returns the rollout plan snapshot from the storage.
36
- Added `initialRolloutPlan` configuration option for standalone client-side SDKs, which allows preloading the SDK storage with a snapshot of the rollout plan.
@@ -51,6 +54,15 @@
5154
- 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.
5255
- Removed the `sync.localhostMode` configuration option to plug the LocalhostMode module.
5356

57+
1.17.1 (July 25, 2025)
58+
- Updated the Redis storage to avoid lazy require of the `ioredis` dependency when the SDK is initialized.
59+
- Updated some transitive dependencies for vulnerability fixes.
60+
- Bugfix - Enhanced HTTP client module to implement timeouts for failing requests that might otherwise remain pending indefinitely on some Fetch API implementations, pausing the SDK synchronization process.
61+
- Bugfix - Properly handle rejected promises when using targeting rules with segment matchers in consumer modes (e.g., Redis and Pluggable storages).
62+
- Bugfix - Sanitize the `SplitSDKMachineName` header value to avoid exceptions on HTTP/S requests when it contains non ISO-8859-1 characters (Related to issue https://github.com/splitio/javascript-client/issues/847).
63+
- Bugfix - Fixed an issue with the SDK_UPDATE event on server-side, where it was not being emitted if there was an empty segment and the SDK received a feature flag update notification.
64+
- Bugfix - Fixed an issue with the server-side polling manager that caused dangling timers when the SDK was destroyed before it was ready.
65+
5466
1.17.0 (September 6, 2024)
5567
- Added `sync.requestOptions.getHeaderOverrides` configuration option to enhance SDK HTTP request Headers for Authorization Frameworks.
5668
- Added `isTimedout` and `lastUpdate` properties to IStatusInterface to keep track of the timestamp of the last SDK event, used on React and Redux SDKs.

README.md

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,24 +24,24 @@ To learn more about Split, contact hello@split.io, or get started with feature f
2424

2525
Split has built and maintains SDKs for:
2626

27-
* .NET [Github](https://github.com/splitio/dotnet-client) [Docs](https://help.split.io/hc/en-us/articles/360020240172--NET-SDK)
28-
* Android [Github](https://github.com/splitio/android-client) [Docs](https://help.split.io/hc/en-us/articles/360020343291-Android-SDK)
29-
* Angular [Github](https://github.com/splitio/angular-sdk-plugin) [Docs](https://help.split.io/hc/en-us/articles/6495326064397-Angular-utilities)
30-
* Elixir thin-client [Github](https://github.com/splitio/elixir-thin-client) [Docs](https://help.split.io/hc/en-us/articles/26988707417869-Elixir-Thin-Client-SDK)
31-
* Flutter [Github](https://github.com/splitio/flutter-sdk-plugin) [Docs](https://help.split.io/hc/en-us/articles/8096158017165-Flutter-plugin)
32-
* GO [Github](https://github.com/splitio/go-client) [Docs](https://help.split.io/hc/en-us/articles/360020093652-Go-SDK)
33-
* iOS [Github](https://github.com/splitio/ios-client) [Docs](https://help.split.io/hc/en-us/articles/360020401491-iOS-SDK)
34-
* Java [Github](https://github.com/splitio/java-client) [Docs](https://help.split.io/hc/en-us/articles/360020405151-Java-SDK)
35-
* JavaScript [Github](https://github.com/splitio/javascript-client) [Docs](https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK)
36-
* JavaScript for Browser [Github](https://github.com/splitio/javascript-browser-client) [Docs](https://help.split.io/hc/en-us/articles/360058730852-Browser-SDK)
37-
* Node.js [Github](https://github.com/splitio/javascript-client) [Docs](https://help.split.io/hc/en-us/articles/360020564931-Node-js-SDK)
38-
* PHP [Github](https://github.com/splitio/php-client) [Docs](https://help.split.io/hc/en-us/articles/360020350372-PHP-SDK)
39-
* PHP thin-client [Github](https://github.com/splitio/php-thin-client) [Docs](https://help.split.io/hc/en-us/articles/18305128673933-PHP-Thin-Client-SDK)
40-
* Python [Github](https://github.com/splitio/python-client) [Docs](https://help.split.io/hc/en-us/articles/360020359652-Python-SDK)
41-
* React [Github](https://github.com/splitio/react-client) [Docs](https://help.split.io/hc/en-us/articles/360038825091-React-SDK)
42-
* React Native [Github](https://github.com/splitio/react-native-client) [Docs](https://help.split.io/hc/en-us/articles/4406066357901-React-Native-SDK)
43-
* Redux [Github](https://github.com/splitio/redux-client) [Docs](https://help.split.io/hc/en-us/articles/360038851551-Redux-SDK)
44-
* Ruby [Github](https://github.com/splitio/ruby-client) [Docs](https://help.split.io/hc/en-us/articles/360020673251-Ruby-SDK)
27+
* .NET [Github](https://github.com/splitio/dotnet-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/server-side-sdks/net-sdk/)
28+
* Android [Github](https://github.com/splitio/android-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/android-sdk/)
29+
* Angular [Github](https://github.com/splitio/angular-sdk-plugin) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/angular-utilities/)
30+
* Elixir thin-client [Github](https://github.com/splitio/elixir-thin-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/server-side-sdks/elixir-thin-client-sdk/)
31+
* Flutter [Github](https://github.com/splitio/flutter-sdk-plugin) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/flutter-plugin/)
32+
* GO [Github](https://github.com/splitio/go-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/server-side-sdks/go-sdk/)
33+
* iOS [Github](https://github.com/splitio/ios-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/ios-sdk/)
34+
* Java [Github](https://github.com/splitio/java-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/server-side-sdks/java-sdk/)
35+
* JavaScript [Github](https://github.com/splitio/javascript-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/javascript-sdk/)
36+
* JavaScript for Browser [Github](https://github.com/splitio/javascript-browser-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/browser-sdk/)
37+
* Node.js [Github](https://github.com/splitio/javascript-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/server-side-sdks/nodejs-sdk/)
38+
* PHP [Github](https://github.com/splitio/php-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/server-side-sdks/php-sdk/)
39+
* PHP thin-client [Github](https://github.com/splitio/php-thin-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/server-side-sdks/php-thin-client-sdk/)
40+
* Python [Github](https://github.com/splitio/python-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/server-side-sdks/python-sdk/)
41+
* React [Github](https://github.com/splitio/react-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/react-sdk/)
42+
* React Native [Github](https://github.com/splitio/react-native-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/react-native-sdk/)
43+
* Redux [Github](https://github.com/splitio/redux-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/redux-sdk/)
44+
* Ruby [Github](https://github.com/splitio/ruby-client) [Docs](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/server-side-sdks/ruby-sdk/)
4545

4646
For a comprehensive list of open source projects visit our [Github page](https://github.com/splitio?utf8=%E2%9C%93&query=%20only%3Apublic%20).
4747

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@splitsoftware/splitio-commons",
3-
"version": "2.5.0",
3+
"version": "2.6.0",
44
"description": "Split JavaScript SDK common components",
55
"main": "cjs/index.js",
66
"module": "esm/index.js",

src/logger/messages/info.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const codesInfo: [number, string][] = codesWarn.concat([
1111
[c.IMPRESSION, c.LOG_PREFIX_IMPRESSIONS_TRACKER +'Feature flag: %s. Key: %s. Evaluation: %s. Label: %s'],
1212
[c.IMPRESSION_QUEUEING, c.LOG_PREFIX_IMPRESSIONS_TRACKER +'Queueing corresponding impression.'],
1313
[c.NEW_SHARED_CLIENT, 'New shared client instance created.'],
14-
[c.NEW_FACTORY, 'New Split SDK instance created.'],
14+
[c.NEW_FACTORY, 'New Split SDK instance created. %s'],
1515
[c.EVENTS_TRACKER_SUCCESS, c.LOG_PREFIX_EVENTS_TRACKER + 'Successfully queued %s'],
1616
[c.IMPRESSIONS_TRACKER_SUCCESS, c.LOG_PREFIX_IMPRESSIONS_TRACKER + 'Successfully stored %s impression(s).'],
1717
[c.USER_CONSENT_UPDATED, 'UserConsent: consent status changed from %s to %s.'],

src/sdkFactory/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ export function sdkFactory(params: ISdkFactoryParams): SplitIO.ISDK | SplitIO.IA
113113
initCallbacks.length = 0;
114114
}
115115

116-
log.info(NEW_FACTORY);
116+
log.info(NEW_FACTORY, [settings.version]);
117117

118118
// @ts-ignore
119119
return objectAssign({

src/storages/__tests__/RBSegmentsCacheSync.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import { IRBSegmentsCacheSync } from '../types';
66
import { fullSettings } from '../../utils/settingsValidation/__tests__/settings.mocks';
77

88
const cacheInMemory = new RBSegmentsCacheInMemory();
9-
const cacheInLocal = new RBSegmentsCacheInLocal(fullSettings, new KeyBuilderCS('SPLITIO', 'user'));
9+
// eslint-disable-next-line no-undef
10+
const cacheInLocal = new RBSegmentsCacheInLocal(fullSettings, new KeyBuilderCS('SPLITIO', 'user'), localStorage);
1011

1112
describe.each([cacheInMemory, cacheInLocal])('Rule-based segments cache sync (Memory & LocalStorage)', (cache: IRBSegmentsCacheSync) => {
1213

src/storages/inLocalStorage/MySegmentsCacheInLocal.ts

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,28 @@ import { isNaNNumber } from '../../utils/lang';
33
import { AbstractMySegmentsCacheSync } from '../AbstractMySegmentsCacheSync';
44
import type { MySegmentsKeyBuilder } from '../KeyBuilderCS';
55
import { LOG_PREFIX, DEFINED } from './constants';
6+
import { StorageAdapter } from '../types';
67

78
export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
89

910
private readonly keys: MySegmentsKeyBuilder;
1011
private readonly log: ILogger;
12+
private readonly storage: StorageAdapter;
1113

12-
constructor(log: ILogger, keys: MySegmentsKeyBuilder) {
14+
constructor(log: ILogger, keys: MySegmentsKeyBuilder, storage: StorageAdapter) {
1315
super();
1416
this.log = log;
1517
this.keys = keys;
18+
this.storage = storage;
1619
// There is not need to flush segments cache like splits cache, since resetSegments receives the up-to-date list of active segments
1720
}
1821

1922
protected addSegment(name: string): boolean {
2023
const segmentKey = this.keys.buildSegmentNameKey(name);
2124

2225
try {
23-
if (localStorage.getItem(segmentKey) === DEFINED) return false;
24-
localStorage.setItem(segmentKey, DEFINED);
26+
if (this.storage.getItem(segmentKey) === DEFINED) return false;
27+
this.storage.setItem(segmentKey, DEFINED);
2528
return true;
2629
} catch (e) {
2730
this.log.error(LOG_PREFIX + e);
@@ -33,8 +36,8 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
3336
const segmentKey = this.keys.buildSegmentNameKey(name);
3437

3538
try {
36-
if (localStorage.getItem(segmentKey) !== DEFINED) return false;
37-
localStorage.removeItem(segmentKey);
39+
if (this.storage.getItem(segmentKey) !== DEFINED) return false;
40+
this.storage.removeItem(segmentKey);
3841
return true;
3942
} catch (e) {
4043
this.log.error(LOG_PREFIX + e);
@@ -43,18 +46,16 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
4346
}
4447

4548
isInSegment(name: string): boolean {
46-
return localStorage.getItem(this.keys.buildSegmentNameKey(name)) === DEFINED;
49+
return this.storage.getItem(this.keys.buildSegmentNameKey(name)) === DEFINED;
4750
}
4851

4952
getRegisteredSegments(): string[] {
50-
// Scan current values from localStorage
51-
return Object.keys(localStorage).reduce((accum, key) => {
52-
let segmentName = this.keys.extractSegmentName(key);
53-
54-
if (segmentName) accum.push(segmentName);
55-
56-
return accum;
57-
}, [] as string[]);
53+
const registeredSegments: string[] = [];
54+
for (let i = 0, len = this.storage.length; i < len; i++) {
55+
const segmentName = this.keys.extractSegmentName(this.storage.key(i)!);
56+
if (segmentName) registeredSegments.push(segmentName);
57+
}
58+
return registeredSegments;
5859
}
5960

6061
getKeysCount() {
@@ -63,16 +64,16 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync {
6364

6465
protected setChangeNumber(changeNumber?: number) {
6566
try {
66-
if (changeNumber) localStorage.setItem(this.keys.buildTillKey(), changeNumber + '');
67-
else localStorage.removeItem(this.keys.buildTillKey());
67+
if (changeNumber) this.storage.setItem(this.keys.buildTillKey(), changeNumber + '');
68+
else this.storage.removeItem(this.keys.buildTillKey());
6869
} catch (e) {
6970
this.log.error(e);
7071
}
7172
}
7273

7374
getChangeNumber() {
7475
const n = -1;
75-
let value: string | number | null = localStorage.getItem(this.keys.buildTillKey());
76+
let value: string | number | null = this.storage.getItem(this.keys.buildTillKey());
7677

7778
if (value !== null) {
7879
value = parseInt(value, 10);

src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,24 @@ import { isFiniteNumber, isNaNNumber, toNumber } from '../../utils/lang';
55
import { setToArray } from '../../utils/lang/sets';
66
import { usesSegments } from '../AbstractSplitsCacheSync';
77
import { KeyBuilderCS } from '../KeyBuilderCS';
8-
import { IRBSegmentsCacheSync } from '../types';
8+
import { IRBSegmentsCacheSync, StorageAdapter } from '../types';
99
import { LOG_PREFIX } from './constants';
1010

1111
export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
1212

1313
private readonly keys: KeyBuilderCS;
1414
private readonly log: ILogger;
15+
private readonly storage: StorageAdapter;
1516

16-
constructor(settings: ISettings, keys: KeyBuilderCS) {
17+
constructor(settings: ISettings, keys: KeyBuilderCS, storage: StorageAdapter) {
1718
this.keys = keys;
1819
this.log = settings.log;
20+
this.storage = storage;
1921
}
2022

2123
clear() {
2224
this.getNames().forEach(name => this.remove(name));
23-
localStorage.removeItem(this.keys.buildRBSegmentsTillKey());
25+
this.storage.removeItem(this.keys.buildRBSegmentsTillKey());
2426
}
2527

2628
update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): boolean {
@@ -31,29 +33,28 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
3133

3234
private setChangeNumber(changeNumber: number) {
3335
try {
34-
localStorage.setItem(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
35-
localStorage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
36+
this.storage.setItem(this.keys.buildRBSegmentsTillKey(), changeNumber + '');
37+
this.storage.setItem(this.keys.buildLastUpdatedKey(), Date.now() + '');
3638
} catch (e) {
3739
this.log.error(LOG_PREFIX + e);
3840
}
3941
}
4042

4143
private updateSegmentCount(diff: number) {
4244
const segmentsCountKey = this.keys.buildSplitsWithSegmentCountKey();
43-
const count = toNumber(localStorage.getItem(segmentsCountKey)) + diff;
44-
// @ts-expect-error
45-
if (count > 0) localStorage.setItem(segmentsCountKey, count);
46-
else localStorage.removeItem(segmentsCountKey);
45+
const count = toNumber(this.storage.getItem(segmentsCountKey)) + diff;
46+
if (count > 0) this.storage.setItem(segmentsCountKey, count + '');
47+
else this.storage.removeItem(segmentsCountKey);
4748
}
4849

4950
private add(rbSegment: IRBSegment): boolean {
5051
try {
5152
const name = rbSegment.name;
5253
const rbSegmentKey = this.keys.buildRBSegmentKey(name);
53-
const rbSegmentFromLocalStorage = localStorage.getItem(rbSegmentKey);
54-
const previous = rbSegmentFromLocalStorage ? JSON.parse(rbSegmentFromLocalStorage) : null;
54+
const rbSegmentFromStorage = this.storage.getItem(rbSegmentKey);
55+
const previous = rbSegmentFromStorage ? JSON.parse(rbSegmentFromStorage) : null;
5556

56-
localStorage.setItem(rbSegmentKey, JSON.stringify(rbSegment));
57+
this.storage.setItem(rbSegmentKey, JSON.stringify(rbSegment));
5758

5859
let usesSegmentsDiff = 0;
5960
if (previous && usesSegments(previous)) usesSegmentsDiff--;
@@ -72,7 +73,7 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
7273
const rbSegment = this.get(name);
7374
if (!rbSegment) return false;
7475

75-
localStorage.removeItem(this.keys.buildRBSegmentKey(name));
76+
this.storage.removeItem(this.keys.buildRBSegmentKey(name));
7677

7778
if (usesSegments(rbSegment)) this.updateSegmentCount(-1);
7879

@@ -84,13 +85,13 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
8485
}
8586

8687
private getNames(): string[] {
87-
const len = localStorage.length;
88+
const len = this.storage.length;
8889
const accum = [];
8990

9091
let cur = 0;
9192

9293
while (cur < len) {
93-
const key = localStorage.key(cur);
94+
const key = this.storage.key(cur);
9495

9596
if (key != null && this.keys.isRBSegmentKey(key)) accum.push(this.keys.extractKey(key));
9697

@@ -101,7 +102,7 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
101102
}
102103

103104
get(name: string): IRBSegment | null {
104-
const item = localStorage.getItem(this.keys.buildRBSegmentKey(name));
105+
const item = this.storage.getItem(this.keys.buildRBSegmentKey(name));
105106
return item && JSON.parse(item);
106107
}
107108

@@ -117,7 +118,7 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
117118

118119
getChangeNumber(): number {
119120
const n = -1;
120-
let value: string | number | null = localStorage.getItem(this.keys.buildRBSegmentsTillKey());
121+
let value: string | number | null = this.storage.getItem(this.keys.buildRBSegmentsTillKey());
121122

122123
if (value !== null) {
123124
value = parseInt(value, 10);
@@ -129,7 +130,7 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync {
129130
}
130131

131132
usesSegments(): boolean {
132-
const storedCount = localStorage.getItem(this.keys.buildSplitsWithSegmentCountKey());
133+
const storedCount = this.storage.getItem(this.keys.buildSplitsWithSegmentCountKey());
133134
const splitsWithSegmentsCount = storedCount === null ? 0 : toNumber(storedCount);
134135

135136
return isFiniteNumber(splitsWithSegmentsCount) ?

0 commit comments

Comments
 (0)