diff --git a/CHANGES.txt b/CHANGES.txt index 735f452b5..62d103e41 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,7 @@ +11.6.0 (September 18, 2025) + - 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`. + - Updated @splitsoftware/splitio-commons package to version 2.6.0. + 11.5.0 (September 10, 2025) - Added `factory.getRolloutPlan()` method for standalone server-side SDK (Node.js), which returns the rollout plan snapshot from the storage. - Added `initialRolloutPlan` configuration option for standalone client-side SDK (Browser), which allows preloading the SDK storage with a snapshot of the rollout plan. @@ -15,7 +19,7 @@ - Updated @splitsoftware/splitio-commons package to version 2.3.0, which optimizes the Redis storage to: - Avoid lazy require of the `ioredis` dependency when the SDK is initialized, and - Flag the SDK as ready from cache immediately to allow queueing feature flag evaluations before SDK_READY event is emitted. - - Bugfix - Enhanced HTTP client module to implement timeouts for failing requests that might otherwise remain pending indefinitely on some Fetch API implementations. + - 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. 11.2.0 (March 28, 2025) - Added a new optional argument to the client `getTreatment` methods to allow passing additional evaluation options, such as a map of properties to append to the generated impressions sent to Split backend. Read more in our docs. @@ -56,13 +60,22 @@ - Removed the deprecated `GOOGLE_ANALYTICS_TO_SPLIT` and `SPLIT_TO_GOOGLE_ANALYTICS` integrations. The `integrations` configuration option has been removed from the SDK factory configuration, along with the associated interfaces in the TypeScript definitions. - Removed the `core.trafficType` configuration option (`SplitIO.IBrowserSettings['core']['trafficType]`) and the `trafficType` parameter from the SDK `client()` method in Browser (`SplitIO.IBrowserSDK['client']`). As a result, traffic types can no longer be bound to SDK clients, and the traffic type must be provided in the `track` method. +10.28.1 (July 25, 2025) + - Updated @splitsoftware/splitio-commons package to version 1.17.1 that includes some vulnerability and bug fixes. + - Updated the Redis storage to avoid lazy require of the `ioredis` dependency when the SDK is initialized. + - 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. + - Bugfix - Properly handle rejected promises when using targeting rules with segment matchers in consumer modes (e.g., Redis and Pluggable storages). + - 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). + - 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. + - Bugfix - Fixed an issue with the server-side polling manager that caused dangling timers when the SDK was destroyed before it was ready. + 10.28.0 (September 6, 2024) - Updated @splitsoftware/splitio-commons package to version 1.17.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. 10.27.0 (June 25, 2024) - - Added `sync.requestOptions.agent` option to SDK configuration for Node.js. This allows passing a custom Node.js HTTP(S) Agent with specific configurations for the SDK requests, like custom TLS settings or a network proxy (See https://help.split.io/hc/en-us/articles/360020564931-Node-js-SDK#proxy). + - Added `sync.requestOptions.agent` option to SDK configuration for Node.js. This allows passing a custom Node.js HTTP(S) Agent with specific configurations for the SDK requests, like custom TLS settings or a network proxy (See https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/server-side-sdks/nodejs-sdk/#proxy). - Updated some transitive dependencies for vulnerability fixes. 10.26.1 (June 14, 2024) diff --git a/MIGRATION-GUIDE.md b/MIGRATION-GUIDE.md index acbc40ec7..3cca5dcf8 100644 --- a/MIGRATION-GUIDE.md +++ b/MIGRATION-GUIDE.md @@ -12,7 +12,7 @@ Below you will find a list of the changes: ### • Removed the `core.trafficType` configuration option (`SplitIO.IBrowserSettings['core']['trafficType]`) and the `trafficType` parameter from the SDK `client()` method in Browser (`SplitIO.IBrowserSDK['client']`). As a result, traffic types can no longer be bound to SDK clients, and the traffic type must be provided in the `track` method -This change was made to align the SDK with the client-side APIs of the [Browser SDK](https://help.split.io/hc/en-us/articles/360058730852-Browser-SDK) and [React Native SDK](https://help.split.io/hc/en-us/articles/4406066357901-React-Native-SDK). +This change was made to align the SDK with the client-side APIs of the [Browser SDK](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/browser-sdk/) and [React Native SDK](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/react-native-sdk/). SDK clients cannot be bound to a traffic type anymore, and so the traffic type must be provided when calling the `client.track` method. For example: @@ -53,7 +53,7 @@ accountClient.track('account', 'my_event'); ### • Removed the deprecated `GOOGLE_ANALYTICS_TO_SPLIT` and `SPLIT_TO_GOOGLE_ANALYTICS` integrations. The `integrations` configuration option has been removed from the SDK factory configuration, along with the associated interfaces in the TypeScript definitions -The Google Analytics integrations were removed since they integrate with the *Google Universal Analytics* library, which was shut down on July 1, 2024, and [replaced by *Google Analytics 4*](https://support.google.com/analytics/answer/11583528?hl=en). Go to Split's [Google Analytics integration guide](https://help.split.io/hc/en-us/articles/360040838752-Google-Analytics) for more information on how to integrate Split with Google Analytics 4. +The Google Analytics integrations were removed since they integrate with the *Google Universal Analytics* library, which was shut down on July 1, 2024, and [replaced by *Google Analytics 4*](https://support.google.com/analytics/answer/11583528?hl=en). Go to [Google Analytics integration guide](https://developer.harness.io/docs/feature-management-experimentation/integrations/google-analytics/) for more information on how to integrate Split with Google Analytics 4. The integrations have stopped being used and maintained, and were removed from the SDK, together with the `integrations` configuration option. If you were using the `integrations` option, you should remove it from your SDK configuration object. diff --git a/README.md b/README.md index 10a4f8e21..28ab6be85 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ client.on(client.Event.SDK_READY, function() { }); ``` -Please refer to [JavaScript SDK (client-side)](https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK) or [Node.js SDK (server-side)](https://help.split.io/hc/en-us/articles/360020564931-Node-js-SDK) to learn about all the functionality provided by our SDK as well as specifics for each environment and the configuration options available for tailoring it to your current application setup. +Please refer to [JavaScript SDK (client-side)](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/javascript-sdk/) or [Node.js SDK (server-side)](https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/server-side-sdks/nodejs-sdk/) to learn about all the functionality provided by our SDK as well as specifics for each environment and the configuration options available for tailoring it to your current application setup. ## Submitting issues The Split team monitors all issues submitted to this [issue tracker](https://github.com/splitio/javascript-client/issues). We encourage you to use this issue tracker to submit any bug reports, feedback, and feature enhancements. We'll do our best to respond in a timely manner. @@ -62,24 +62,25 @@ To learn more about Split, contact hello@split.io, or get started with feature f Split has built and maintains SDKs for: -* .NET [Github](https://github.com/splitio/dotnet-client) [Docs](https://help.split.io/hc/en-us/articles/360020240172--NET-SDK) -* Android [Github](https://github.com/splitio/android-client) [Docs](https://help.split.io/hc/en-us/articles/360020343291-Android-SDK) -* Angular [Github](https://github.com/splitio/angular-sdk-plugin) [Docs](https://help.split.io/hc/en-us/articles/6495326064397-Angular-utilities) -* 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) -* Flutter [Github](https://github.com/splitio/flutter-sdk-plugin) [Docs](https://help.split.io/hc/en-us/articles/8096158017165-Flutter-plugin) -* GO [Github](https://github.com/splitio/go-client) [Docs](https://help.split.io/hc/en-us/articles/360020093652-Go-SDK) -* iOS [Github](https://github.com/splitio/ios-client) [Docs](https://help.split.io/hc/en-us/articles/360020401491-iOS-SDK) -* Java [Github](https://github.com/splitio/java-client) [Docs](https://help.split.io/hc/en-us/articles/360020405151-Java-SDK) -* JavaScript [Github](https://github.com/splitio/javascript-client) [Docs](https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK) -* JavaScript for Browser [Github](https://github.com/splitio/javascript-browser-client) [Docs](https://help.split.io/hc/en-us/articles/360058730852-Browser-SDK) -* Node.js [Github](https://github.com/splitio/javascript-client) [Docs](https://help.split.io/hc/en-us/articles/360020564931-Node-js-SDK) -* PHP [Github](https://github.com/splitio/php-client) [Docs](https://help.split.io/hc/en-us/articles/360020350372-PHP-SDK) -* 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) -* Python [Github](https://github.com/splitio/python-client) [Docs](https://help.split.io/hc/en-us/articles/360020359652-Python-SDK) -* React [Github](https://github.com/splitio/react-client) [Docs](https://help.split.io/hc/en-us/articles/360038825091-React-SDK) -* React Native [Github](https://github.com/splitio/react-native-client) [Docs](https://help.split.io/hc/en-us/articles/4406066357901-React-Native-SDK) -* Redux [Github](https://github.com/splitio/redux-client) [Docs](https://help.split.io/hc/en-us/articles/360038851551-Redux-SDK) -* Ruby [Github](https://github.com/splitio/ruby-client) [Docs](https://help.split.io/hc/en-us/articles/360020673251-Ruby-SDK) + +* .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/) +* 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/) +* 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/) +* 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/) +* 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/) +* 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/) +* 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/) +* 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/) +* 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/) +* 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/) +* 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/) +* 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/) +* 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/) +* 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/) +* 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/) +* 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/) +* 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/) +* 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/) For a comprehensive list of open source projects visit our [Github page](https://github.com/splitio?utf8=%E2%9C%93&query=%20only%3Apublic%20). diff --git a/package-lock.json b/package-lock.json index 3db49900d..288f018b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@splitsoftware/splitio", - "version": "11.5.0", + "version": "11.6.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio", - "version": "11.5.0", + "version": "11.6.0", "license": "Apache-2.0", "dependencies": { - "@splitsoftware/splitio-commons": "2.5.0", + "@splitsoftware/splitio-commons": "2.6.0", "bloom-filters": "^3.0.4", "ioredis": "^4.28.0", "js-yaml": "^3.13.1", @@ -351,9 +351,9 @@ "dev": true }, "node_modules/@splitsoftware/splitio-commons": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.5.0.tgz", - "integrity": "sha512-46PN2ix62/eHi4LpuBlS/hN1zcuKzLRTznHaUawehDWz3faSvHLTgIN/AplhJ5p43gSuan/5GyQ9dny/ig0eaQ==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.6.0.tgz", + "integrity": "sha512-0xODXLciIvHSuMlb8eukIB2epb3ZyGOsrwS0cMuTdxEvCqr7Nuc9pWDdJtRuN1UwL/jIjBnpDYAc8s6mpqLX2g==", "license": "Apache-2.0", "dependencies": { "@types/ioredis": "^4.28.0", @@ -7740,9 +7740,9 @@ "dev": true }, "@splitsoftware/splitio-commons": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.5.0.tgz", - "integrity": "sha512-46PN2ix62/eHi4LpuBlS/hN1zcuKzLRTznHaUawehDWz3faSvHLTgIN/AplhJ5p43gSuan/5GyQ9dny/ig0eaQ==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@splitsoftware/splitio-commons/-/splitio-commons-2.6.0.tgz", + "integrity": "sha512-0xODXLciIvHSuMlb8eukIB2epb3ZyGOsrwS0cMuTdxEvCqr7Nuc9pWDdJtRuN1UwL/jIjBnpDYAc8s6mpqLX2g==", "requires": { "@types/ioredis": "^4.28.0", "tslib": "^2.3.1" diff --git a/package.json b/package.json index 2ce4da374..c07ef3afc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio", - "version": "11.5.0", + "version": "11.6.0", "description": "Split SDK", "files": [ "README.md", @@ -38,7 +38,7 @@ "node": ">=14.0.0" }, "dependencies": { - "@splitsoftware/splitio-commons": "2.5.0", + "@splitsoftware/splitio-commons": "2.6.0", "bloom-filters": "^3.0.4", "ioredis": "^4.28.0", "js-yaml": "^3.13.1", diff --git a/src/__tests__/browserSuites/push-synchronization-retries.spec.js b/src/__tests__/browserSuites/push-synchronization-retries.spec.js index b9c36bdb1..3065275e3 100644 --- a/src/__tests__/browserSuites/push-synchronization-retries.spec.js +++ b/src/__tests__/browserSuites/push-synchronization-retries.spec.js @@ -135,7 +135,7 @@ export function testSynchronizationRetries(fetchMock, assert) { }); // initial auth - fetchMock.getOnce(url(settings, `/v2/auth?s=1.3&users=${encodeURIComponent(userKey)}&users=${encodeURIComponent(otherUserKeySync)}`), function (url, opts) { + fetchMock.getOnce(url(settings, `/v2/auth?s=1.3&users=${encodeURIComponent(otherUserKeySync)}&users=${encodeURIComponent(userKey)}`), function (url, opts) { if (!opts.headers['Authorization']) assert.fail('`/v2/auth` request must include `Authorization` header'); assert.pass('auth success'); return { status: 200, body: authPushEnabledNicolas }; @@ -144,7 +144,7 @@ export function testSynchronizationRetries(fetchMock, assert) { // initial split and memberships sync fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=-1&rbSince=-1'), { status: 200, body: splitChangesMock1 }); fetchMock.getOnce(url(settings, '/memberships/nicolas%40split.io'), { status: 200, body: membershipsNicolasMock1 }); - fetchMock.get({ url: url(settings, '/memberships/marcio%40split.io'), repeat: 3 }, { status: 200, body: membershipsMarcio }); + fetchMock.get({ url: url(settings, '/memberships/marcio%40split.io'), repeat: 4 }, { status: 200, body: membershipsMarcio }); // split and segment sync after SSE opened fetchMock.getOnce(url(settings, '/splitChanges?s=1.3&since=1457552620999&rbSince=100'), function () { diff --git a/src/__tests__/browserSuites/ready-from-cache-async-wrapper.spec.js b/src/__tests__/browserSuites/ready-from-cache-async-wrapper.spec.js new file mode 100644 index 000000000..73350b3c6 --- /dev/null +++ b/src/__tests__/browserSuites/ready-from-cache-async-wrapper.spec.js @@ -0,0 +1,417 @@ +import sinon from 'sinon'; +import { nearlyEqual } from '../testUtils'; +import { SplitFactory } from '../../'; + +import splitChangesMock1 from '../mocks/splitchanges.since.-1.json'; +import splitChangesMock2 from '../mocks/splitchanges.since.1457552620999.json'; +import membershipsNicolas from '../mocks/memberships.nicolas@split.io.json'; + +import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS, alwaysOnSplitInverted, splitDeclarations, baseConfig, expectedHashNullFilter } from './ready-from-cache.spec'; + +const customWrapper = (() => { + let cache = {}; + return { + getItem(key) { + return Promise.resolve(cache[key] || null); + }, + setItem(key, value) { + cache[key] = value; + return Promise.resolve(); + }, + removeItem(key) { + delete cache[key]; + return Promise.resolve(); + }, + + // For testing purposes: + clear() { + cache = {}; + }, + getCache() { + return cache; + } + }; +})(); + +export default function (fetchMock, assert) { + + assert.test(t => { // Testing when we start from scratch + const testUrls = { + sdk: 'https://sdk.baseurl/readyFromCacheWrapperEmpty', + events: 'https://events.baseurl/readyFromCacheWrapperEmpty' + }; + customWrapper.clear(); + 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': {} } }); + + const splitio = SplitFactory({ + ...baseConfig, + core: { + ...baseConfig.core, + authorizationKey: '', + }, + storage: { + type: 'LOCALSTORAGE', + prefix: 'readyFromCache_1', + wrapper: customWrapper + }, + urls: testUrls + }); + const client = splitio.client(); + const client2 = splitio.client('nicolas2@split.io'); + const client3 = splitio.client('nicolas3@split.io'); + + client.once(client.Event.SDK_READY_TIMED_OUT, () => { + t.fail('It should not timeout in this scenario.'); + t.end(); + }); + client.once(client.Event.SDK_READY_FROM_CACHE, () => { + t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY'); + }); + + client.on(client.Event.SDK_READY, () => { + t.true(client.__getStatus().isReadyFromCache, 'Client should emit SDK_READY and it should be ready from cache'); + }); + client2.on(client.Event.SDK_READY, () => { + t.true(client2.__getStatus().isReadyFromCache, 'Non-default client should emit SDK_READY and it should be ready from cache'); + }); + client3.on(client.Event.SDK_READY, () => { + t.true(client2.__getStatus().isReadyFromCache, 'Non-default client should emit SDK_READY and it should be ready from cache'); + }); + + }); + + assert.test(t => { // Testing when we start with cached data and not expired (lastUpdate timestamp higher than default (10) expirationDays ago) + const testUrls = { + sdk: 'https://sdk.baseurl/readyFromCacheWrapperWithData3', + events: 'https://events.baseurl/readyFromCacheWrapperWithData3' + }; + customWrapper.clear(); + t.plan(12 * 2 + 5); + + fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=25&rbSince=-1', () => { + t.equal(JSON.parse(customWrapper.getCache()['readyFromCache_3.SPLITIO'])['readyFromCache_3.SPLITIO.split.always_on'], alwaysOnSplitInverted, 'feature flags must not be cleaned from cache'); + return new Promise(res => { setTimeout(() => res({ status: 200, body: { ff: { ...splitChangesMock1.ff, s: 25 } }, headers: {} }), 200); }); // 400ms is how long it'll take to reply with Splits, no SDK_READY should be emitted before that. + }); + fetchMock.get(testUrls.sdk + '/memberships/nicolas%40split.io', () => { + return new Promise(res => { setTimeout(() => res({ status: 200, body: membershipsNicolas, headers: {} }), 400); }); // First client gets segments before splits. No segment cache loading (yet) + }); + fetchMock.get(testUrls.sdk + '/memberships/nicolas2%40split.io', () => { + return new Promise(res => { setTimeout(() => res({ status: 200, body: { 'ms': {} }, headers: {} }), 700); }); // Second client gets segments after 700ms + }); + fetchMock.get(testUrls.sdk + '/memberships/nicolas3%40split.io', () => { + return new Promise(res => { setTimeout(() => res({ status: 200, body: { 'ms': {} }, headers: {} }), 1000); }); // Third client memberships will come after 1s + }); + fetchMock.get(testUrls.sdk + '/memberships/nicolas4%40split.io', { 'ms': {} }); + fetchMock.postOnce(testUrls.events + '/testImpressions/bulk', 200); + fetchMock.postOnce(testUrls.events + '/testImpressions/count', 200); + + + customWrapper.getCache()['some_user_item'] = 'user_item'; + customWrapper.getCache()['readyFromCache_3.SPLITIO'] = JSON.stringify({ + 'readyFromCache_3.SPLITIO.splits.till': '25', + 'readyFromCache_3.SPLITIO.splits.lastUpdated': Date.now().toString(), + 'readyFromCache_3.SPLITIO.split.always_on': alwaysOnSplitInverted, + 'readyFromCache_3.SPLITIO.hash': expectedHashNullFilter + }); + + const startTime = Date.now(); + const splitio = SplitFactory({ + ...baseConfig, + storage: { + type: 'LOCALSTORAGE', + prefix: 'readyFromCache_3', + wrapper: customWrapper + }, + startup: { + readyTimeout: 0.85 + }, + urls: testUrls, + }); + const client = splitio.client(); + const client2 = splitio.client('nicolas2@split.io'); + const client3 = splitio.client('nicolas3@split.io'); + + t.equal(client.getTreatment('always_on'), 'control', 'It should evaluate control treatments if not ready neither by cache nor the cloud'); + t.equal(client3.getTreatment('always_on'), 'control', 'It should evaluate control treatments if not ready neither by cache nor the cloud'); + + client.on(client.Event.SDK_READY_TIMED_OUT, () => { + t.fail('It should not timeout in this scenario.'); + t.end(); + }); + + client.on(client.Event.SDK_READY_FROM_CACHE, () => { + t.true(Date.now() - startTime < 400, 'It should emit SDK_READY_FROM_CACHE on every client if there was data in the cache and we subscribe on time. Should be considerably faster than actual readiness from the cloud.'); + t.equal(client.getTreatment('always_on'), 'off', 'It should evaluate treatments with data from cache instead of control due to Input Validation'); + + const client4 = splitio.client('nicolas4@split.io'); + t.equal(client4.getTreatment('always_on'), 'off', 'It should evaluate treatments with data from cache instead of control'); + + client4.on(client4.Event.SDK_READY_FROM_CACHE, () => { + t.fail('It should not emit SDK_READY_FROM_CACHE if already done.'); + }); + }); + client2.on(client2.Event.SDK_READY_FROM_CACHE, () => { + t.true(Date.now() - startTime < 400, 'It should emit SDK_READY_FROM_CACHE on every client if there was data in the cache and we subscribe on time. Should be considerably faster than actual readiness from the cloud.'); + t.equal(client2.getTreatment('always_on'), 'off', 'It should evaluate treatments with data from cache instead of control due to Input Validation'); + }); + client3.on(client3.Event.SDK_READY_FROM_CACHE, () => { + t.true(Date.now() - startTime < 400, 'It should emit SDK_READY_FROM_CACHE on every client if there was data in the cache and we subscribe on time. Should be considerably faster than actual readiness from the cloud.'); + t.equal(client3.getTreatment('always_on'), 'off', 'It should evaluate treatments with data from cache instead of control due to Input Validation'); + }); + + client.on(client.Event.SDK_READY, () => { + t.true(Date.now() - startTime >= 400, 'It should emit SDK_READY too but after syncing with the cloud.'); + t.equal(client.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + }); + client.ready().then(() => { + t.true(Date.now() - startTime >= 400, 'It should resolve ready promise after syncing with the cloud.'); + t.equal(client.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + }); + client2.on(client2.Event.SDK_READY, () => { + t.true(Date.now() - startTime >= 700, 'It should emit SDK_READY too but after syncing with the cloud.'); + t.equal(client2.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + }); + client2.ready().then(() => { + t.true(Date.now() - startTime >= 700, 'It should resolve ready promise after syncing with the cloud.'); + t.equal(client2.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + }); + client3.on(client3.Event.SDK_READY, () => { + client3.ready().then(() => { + t.true(Date.now() - startTime >= 1000, 'It should resolve ready promise after syncing with the cloud.'); + t.equal(client3.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + + // Last cb: destroy clients and check that storage wrapper has the expected items + Promise.all([client3.destroy(), client2.destroy(), client.destroy()]).then(() => { + t.equal(customWrapper.getCache()['some_user_item'], 'user_item', 'user items at storage wrapper must not be changed'); + t.equal(JSON.parse(customWrapper.getCache()['readyFromCache_3.SPLITIO'])['readyFromCache_3.SPLITIO.splits.till'], '1457552620999', 'splits.till must correspond to the till of the last successfully fetched Splits'); + t.true(nearlyEqual(parseInt(JSON.parse(customWrapper.getCache()['readyFromCache_3.SPLITIO'])['readyFromCache_3.SPLITIO.splits.lastUpdated']), Date.now() - 800 /* 800 ms between last Split and memberships fetch */), 'lastUpdated must correspond to the timestamp of the last successfully fetched Splits'); + }); + }); + t.true(Date.now() - startTime >= 1000, 'It should emit SDK_READY too but after syncing with the cloud.'); + t.equal(client3.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + }); + client3.on(client3.Event.SDK_READY_TIMED_OUT, () => { + client3.ready().catch(() => { + t.true(Date.now() - startTime >= 850, 'It should reject ready promise before syncing memberships data with the cloud.'); + t.equal(client3.getTreatment('always_on'), 'on', 'It should evaluate treatments with memberships data from cache.'); + }); + t.true(Date.now() - startTime >= 850, 'It should emit SDK_READY_TIMED_OUT before syncing memberships data with the cloud.'); + t.equal(client3.getTreatment('always_on'), 'on', 'It should evaluate treatments with memberships data from cache.'); + }); + }); + + assert.test(t => { // Testing when we start with cached data but expired (lastUpdate timestamp lower than custom (1) expirationDays ago) + const CLIENT_READY_MS = 400, CLIENT2_READY_MS = 700, CLIENT3_READY_MS = 1000; + + const testUrls = { + sdk: 'https://sdk.baseurl/readyFromCacheWrapperWithData4', + events: 'https://events.baseurl/readyFromCacheWrapperWithData4' + }; + customWrapper.clear(); + + fetchMock.get(testUrls.sdk + '/splitChanges?s=1.3&since=-1&rbSince=-1', () => { + const cache = JSON.parse(customWrapper.getCache()['readyFromCache_4.SPLITIO']); + t.equal(cache['readyFromCache_4.SPLITIO.hash'], expectedHashNullFilter, 'storage hash must not be changed'); + t.true(nearlyEqual(parseInt(cache['readyFromCache_4.SPLITIO.lastClear'], 10), Date.now()), 'storage lastClear timestamp must be updated'); + t.equal(Object.keys(cache).length, 2, 'feature flags cache data must be cleaned from storage wrapper'); + return { status: 200, body: splitChangesMock1 }; + }); + fetchMock.get(testUrls.sdk + '/memberships/nicolas%40split.io', () => { + return new Promise(res => { setTimeout(() => res({ status: 200, body: membershipsNicolas, headers: {} }), CLIENT_READY_MS); }); // First client gets segments before splits. No segment cache loading (yet) + }); + fetchMock.get(testUrls.sdk + '/memberships/nicolas2%40split.io', () => { + return new Promise(res => { setTimeout(() => res({ status: 200, body: { 'ms': {} }, headers: {} }), CLIENT2_READY_MS); }); // Second client gets segments after 700ms + }); + fetchMock.get(testUrls.sdk + '/memberships/nicolas3%40split.io', () => { + return new Promise(res => { setTimeout(() => res({ status: 200, body: { 'ms': {} }, headers: {} }), CLIENT3_READY_MS); }); // Third client memberships will come after 1s + }); + fetchMock.postOnce(testUrls.events + '/testImpressions/bulk', 200); + fetchMock.postOnce(testUrls.events + '/testImpressions/count', 200); + + customWrapper.getCache()['readyFromCache_4.SPLITIO'] = JSON.stringify({ + 'readyFromCache_4.SPLITIO.splits.till': '25', + 'readyFromCache_4.SPLITIO.splits.lastUpdated': Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS / 10 - 1, // -1 to ensure having an expired lastUpdated item + 'readyFromCache_4.SPLITIO.split.always_on': alwaysOnSplitInverted, + 'readyFromCache_4.SPLITIO.hash': expectedHashNullFilter + }); + + const startTime = Date.now(); + const splitio = SplitFactory({ + ...baseConfig, + storage: { + type: 'LOCALSTORAGE', + prefix: 'readyFromCache_4', + expirationDays: 1, + wrapper: customWrapper + }, + startup: { + readyTimeout: 0.85 + }, + urls: testUrls, + }); + const client = splitio.client(); + const client2 = splitio.client('nicolas2@split.io'); + const client3 = splitio.client('nicolas3@split.io'); + + t.equal(client.getTreatment('always_on'), 'control', 'It should evaluate control treatments if not ready neither by cache nor the cloud'); + t.equal(client3.getTreatment('always_on'), 'control', 'It should evaluate control treatments if not ready neither by cache nor the cloud'); + + client.once(client.Event.SDK_READY_TIMED_OUT, () => { + t.fail('It should not timeout in this scenario.'); + t.end(); + }); + + client.once(client.Event.SDK_READY_FROM_CACHE, () => { + t.true(nearlyEqual(Date.now() - startTime, CLIENT_READY_MS), 'It should emit SDK_READY_FROM_CACHE alongside SDK_READY'); + }); + client2.once(client2.Event.SDK_READY_FROM_CACHE, () => { + t.true(nearlyEqual(Date.now() - startTime, CLIENT2_READY_MS), 'It should emit SDK_READY_FROM_CACHE alongside SDK_READY'); + }); + client3.once(client3.Event.SDK_READY_FROM_CACHE, () => { + t.true(nearlyEqual(Date.now() - startTime, CLIENT3_READY_MS), 'It should emit SDK_READY_FROM_CACHE alongside SDK_READY'); + }); + + client.on(client.Event.SDK_READY, () => { + t.true(nearlyEqual(Date.now() - startTime, CLIENT_READY_MS), 'It should emit SDK_READY after syncing with the cloud.'); + t.equal(client.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + }); + client.ready().then(() => { + t.true(nearlyEqual(Date.now() - startTime, CLIENT_READY_MS), 'It should resolve ready promise after syncing with the cloud.'); + t.equal(client.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + }); + client2.on(client2.Event.SDK_READY, () => { + t.true(nearlyEqual(Date.now() - startTime, CLIENT2_READY_MS), 'It should emit SDK_READY after syncing with the cloud.'); + t.equal(client2.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + }); + client2.ready().then(() => { + t.true(nearlyEqual(Date.now() - startTime, CLIENT2_READY_MS), 'It should resolve ready promise after syncing with the cloud.'); + t.equal(client2.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + }); + client3.on(client3.Event.SDK_READY, () => { + client3.ready().then(() => { + t.true(nearlyEqual(Date.now() - startTime, CLIENT3_READY_MS), 'It should resolve ready promise after syncing with the cloud.'); + t.equal(client3.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + + // Last cb: destroy clients and check that storage wrapper has the expected items + Promise.all([client3.destroy(), client2.destroy(), client.destroy()]).then(() => { + const cache = JSON.parse(customWrapper.getCache()['readyFromCache_4.SPLITIO']); + t.equal(cache['readyFromCache_4.SPLITIO.splits.till'], '1457552620999', 'splits.till must correspond to the till of the last successfully fetched Splits'); + t.true(nearlyEqual(parseInt(cache['readyFromCache_4.SPLITIO.splits.lastUpdated']), Date.now() - 1000 /* 1000 ms between last Split and memberships fetch */), 'lastUpdated must correspond to the timestamp of the last successfully fetched Splits'); + + t.end(); + }); + }); + t.true(Date.now() - startTime >= 1000, 'It should emit SDK_READY after syncing with the cloud.'); + t.equal(client3.getTreatment('always_on'), 'on', 'It should evaluate treatments with updated data after syncing with the cloud.'); + }); + client3.on(client3.Event.SDK_READY_TIMED_OUT, () => { + client3.ready().catch(() => { + t.true(Date.now() - startTime >= 850, 'It should reject ready promise before syncing memberships data with the cloud.'); + t.equal(client3.getTreatment('always_on'), 'control', 'It should not evaluate treatments with memberships data from cache.'); + }); + t.true(Date.now() - startTime >= 850, 'It should emit SDK_READY_TIMED_OUT before syncing memberships data with the cloud.'); + t.equal(client3.getTreatment('always_on'), 'control', 'It should evaluate treatments with memberships data from cache.'); + }); + }); + + assert.test(async t => { // Testing clearOnInit config true + sinon.spy(console, 'log'); + + const testUrls = { + sdk: 'https://sdk.baseurl/readyFromCacheWrapper_10', + events: 'https://events.baseurl/readyFromCacheWrapper_10' + }; + const clearOnInitConfig = { + ...baseConfig, + storage: { + type: 'LOCALSTORAGE', + prefix: 'readyFromCache_10', + clearOnInit: true, + wrapper: customWrapper + }, + urls: testUrls, + debug: true + }; + + // Start with cached data but without lastClear item (JS SDK below 11.1.0) -> cache cleanup + customWrapper.clear(); + customWrapper.getCache()['readyFromCache_10.SPLITIO'] = JSON.stringify({ + 'readyFromCache_10.SPLITIO.splits.till': '25', + 'readyFromCache_10.SPLITIO.split.p1__split': JSON.stringify(splitDeclarations.p1__split), + 'readyFromCache_10.SPLITIO.split.p2__split': JSON.stringify(splitDeclarations.p2__split), + 'readyFromCache_10.SPLITIO.hash': expectedHashNullFilter + }); + + 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=1457552620999&rbSince=100', { status: 200, body: splitChangesMock2 }); + fetchMock.get(testUrls.sdk + '/memberships/nicolas%40split.io', { status: 200, body: { ms: {} } }); + + let splitio = SplitFactory(clearOnInitConfig); + let client = splitio.client(); + let manager = splitio.manager(); + + client.once(client.Event.SDK_READY_FROM_CACHE, () => { + t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY, because clearOnInit is true'); + }); + + await client.ready(); + + t.true(console.log.calledWithMatch('clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache'), 'It should log a message about cleaning up cache'); + + t.equal(manager.names().sort().length, 36, 'active splits should be present for evaluation'); + + await splitio.destroy(); + const cache = JSON.parse(customWrapper.getCache()['readyFromCache_10.SPLITIO']); + t.equal(cache['readyFromCache_10.SPLITIO.splits.till'], '1457552620999', 'splits.till must correspond to the till of the last successfully fetched Splits'); + t.equal(cache['readyFromCache_10.SPLITIO.hash'], expectedHashNullFilter, 'Storage hash must not be changed'); + t.true(nearlyEqual(parseInt(cache['readyFromCache_10.SPLITIO.lastClear']), Date.now()), 'lastClear timestamp must be set'); + + // Start again with cached data and lastClear item within the last 24 hours -> no cache cleanup + console.log.resetHistory(); + fetchMock.getOnce(testUrls.sdk + '/splitChanges?s=1.3&since=1457552620999&rbSince=-1', { status: 200, body: splitChangesMock2 }); + + splitio = SplitFactory(clearOnInitConfig); + client = splitio.client(); + manager = splitio.manager(); + + await new Promise(res => client.once(client.Event.SDK_READY_FROM_CACHE, res)); + + t.equal(manager.names().sort().length, 36, 'active splits should be present for evaluation'); + t.false(console.log.calledWithMatch('clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache'), 'It should log a message about cleaning up cache'); + + await splitio.destroy(); + + // Start again with cached data and lastClear item older than 24 hours -> cache cleanup + console.log.resetHistory(); + customWrapper.getCache()['readyFromCache_10.SPLITIO'] = JSON.stringify({ + ...JSON.parse(customWrapper.getCache()['readyFromCache_10.SPLITIO']), + 'readyFromCache_10.SPLITIO.lastClear': Date.now() - 25 * 60 * 60 * 1000 // 25 hours ago + }); + 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=1457552620999&rbSince=-1', { status: 200, body: splitChangesMock2 }); + + splitio = SplitFactory(clearOnInitConfig); + client = splitio.client(); + manager = splitio.manager(); + + client.once(client.Event.SDK_READY_FROM_CACHE, () => { + t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY, because clearOnInit is true'); + }); + + await new Promise(res => client.once(client.Event.SDK_READY, res)); + + t.equal(manager.names().sort().length, 36, 'active splits should be present for evaluation'); + t.true(console.log.calledWithMatch('clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache'), 'It should log a message about cleaning up cache'); + + await splitio.destroy(); + + console.log.restore(); + t.end(); + }); + +} diff --git a/src/__tests__/browserSuites/ready-from-cache.spec.js b/src/__tests__/browserSuites/ready-from-cache.spec.js index 9383e360b..23053b239 100644 --- a/src/__tests__/browserSuites/ready-from-cache.spec.js +++ b/src/__tests__/browserSuites/ready-from-cache.spec.js @@ -8,7 +8,7 @@ import splitChangesMock2 from '../mocks/splitchanges.since.1457552620999.json'; import membershipsNicolas from '../mocks/memberships.nicolas@split.io.json'; -const DEFAULT_CACHE_EXPIRATION_IN_MILLIS = 864000000; // 10 days +export const DEFAULT_CACHE_EXPIRATION_IN_MILLIS = 864000000; // 10 days export const alwaysOnSplitInverted = JSON.stringify({ 'environment': null, @@ -67,7 +67,7 @@ export const alwaysOnSplitInverted = JSON.stringify({ ] }); -const splitDeclarations = { +export const splitDeclarations = { p1__split: { 'name': 'p1__split', 'status': 'ACTIVE', @@ -85,7 +85,7 @@ const splitDeclarations = { }, }; -const baseConfig = { +export const baseConfig = { core: { authorizationKey: '', key: 'nicolas@split.io' @@ -103,8 +103,8 @@ const baseConfig = { streamingEnabled: false }; -const expectedHashNullFilter = '193e6f3f'; // for SDK key '', filter query null, and flags spec version '1.3' -const expectedHashWithFilter = '2ce5cc38'; // for SDK key '', filter query '&names=p1__split,p2__split', and flags spec version '1.3' +export const expectedHashNullFilter = '193e6f3f'; // for SDK key '', filter query null, and flags spec version '1.3' +export const expectedHashWithFilter = '2ce5cc38'; // for SDK key '', filter query '&names=p1__split,p2__split', and flags spec version '1.3' export default function (fetchMock, assert) { @@ -894,13 +894,14 @@ export default function (fetchMock, assert) { let client = splitio.client(); let manager = splitio.manager(); - t.true(console.log.calledWithMatch('clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache'), 'It should log a message about cleaning up cache'); - client.once(client.Event.SDK_READY_FROM_CACHE, () => { t.true(client.__getStatus().isReady, 'Client should emit SDK_READY_FROM_CACHE alongside SDK_READY, because clearOnInit is true'); }); await client.ready(); + + t.true(console.log.calledWithMatch('clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache'), 'It should log a message about cleaning up cache'); + t.equal(manager.names().sort().length, 36, 'active splits should be present for evaluation'); await splitio.destroy(); diff --git a/src/__tests__/online/browser.spec.js b/src/__tests__/online/browser.spec.js index 3f2051374..e98a46490 100644 --- a/src/__tests__/online/browser.spec.js +++ b/src/__tests__/online/browser.spec.js @@ -10,6 +10,7 @@ import telemetrySuite from '../browserSuites/telemetry.spec'; import impressionsListenerSuite from '../browserSuites/impressions-listener.spec'; import readinessSuite from '../browserSuites/readiness.spec'; import readyFromCache from '../browserSuites/ready-from-cache.spec'; +import readyFromCacheAsyncWrapper from '../browserSuites/ready-from-cache-async-wrapper.spec'; import { withoutBindingTT } from '../browserSuites/events.spec'; import sharedInstantiationSuite from '../browserSuites/shared-instantiation.spec'; import managerSuite from '../browserSuites/manager.spec'; @@ -127,6 +128,7 @@ tape('## E2E CI Tests ##', function (assert) { assert.test('E2E / Use Beacon API DEBUG (or Fetch if not available) to send remaining impressions and events when browser page is unload or hidden', useBeaconDebugApiSuite.bind(null, fetchMock)); /* Validate ready from cache behavior (might be merged into another suite if we end up having simple behavior around it as expected) */ assert.test('E2E / Readiness from cache', readyFromCache.bind(null, fetchMock)); + assert.test('E2E / Readiness from cache with custom async wrapper', readyFromCacheAsyncWrapper.bind(null, fetchMock)); /* Validate readiness with ready promises */ assert.test('E2E / Ready promise', readyPromiseSuite.bind(null, fetchMock)); /* Validate fetching specific splits */ diff --git a/src/factory/node.js b/src/factory/node.js index 6054c3467..ae4ff1c29 100644 --- a/src/factory/node.js +++ b/src/factory/node.js @@ -55,8 +55,8 @@ function getModules(settings) { extraProps: (params) => { if (!isConsumerMode(params.settings.mode)) { return { - getRolloutPlan(userKeys) { - return getRolloutPlan(params.settings.log, params.storage, userKeys); + getRolloutPlan(options) { + return getRolloutPlan(params.settings.log, params.storage, options); } }; } diff --git a/src/platform/getEventSource/eventsource.js b/src/platform/getEventSource/eventsource.js index 15e750f47..36a23d3f3 100644 --- a/src/platform/getEventSource/eventsource.js +++ b/src/platform/getEventSource/eventsource.js @@ -6,7 +6,7 @@ that accepts a custom agent. The MIT License -Copyright (c) EventSource GitHub organisation +Copyright (c) EventSource GitHub organization Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/src/settings/defaults/version.js b/src/settings/defaults/version.js index 937adff92..d85530ba7 100644 --- a/src/settings/defaults/version.js +++ b/src/settings/defaults/version.js @@ -1 +1 @@ -export const packageVersion = '11.5.0'; +export const packageVersion = '11.6.0'; diff --git a/src/settings/storage/browser.js b/src/settings/storage/browser.js index 0165d45e3..eb992d78d 100644 --- a/src/settings/storage/browser.js +++ b/src/settings/storage/browser.js @@ -1,4 +1,3 @@ -import { isLocalStorageAvailable } from '@splitsoftware/splitio-commons/src/utils/env/isLocalStorageAvailable'; import { LOCALHOST_MODE, STORAGE_MEMORY } from '@splitsoftware/splitio-commons/src/utils/constants'; const STORAGE_LOCALSTORAGE = 'LOCALSTORAGE'; @@ -12,7 +11,8 @@ export function validateStorage(settings) { options = {}, prefix, expirationDays, - clearOnInit + clearOnInit, + wrapper } = { type: STORAGE_MEMORY }, } = settings; let __originalType; @@ -28,12 +28,10 @@ export function validateStorage(settings) { fallbackToMemory(); } - // If an invalid storage type is provided OR we want to use LOCALSTORAGE and - // it's not available, fallback into MEMORY - if (type !== STORAGE_MEMORY && type !== STORAGE_LOCALSTORAGE || - type === STORAGE_LOCALSTORAGE && !isLocalStorageAvailable()) { + // If an invalid storage type is provided, fallback into MEMORY + if (type !== STORAGE_MEMORY && type !== STORAGE_LOCALSTORAGE) { fallbackToMemory(); - log.error('Invalid or unavailable storage. Fallback into MEMORY storage'); + log.error('Invalid storage type. Fallback into MEMORY storage'); } return { @@ -42,6 +40,7 @@ export function validateStorage(settings) { prefix, expirationDays, clearOnInit, + wrapper, __originalType }; } diff --git a/ts-tests/index.ts b/ts-tests/index.ts index ad9527b8a..d306a9294 100644 --- a/ts-tests/index.ts +++ b/ts-tests/index.ts @@ -566,7 +566,8 @@ let fullBrowserSettings: SplitIO.IBrowserSettings = { type: 'LOCALSTORAGE', prefix: 'PREFIX', expirationDays: 1, - clearOnInit: true + clearOnInit: true, + wrapper: window.sessionStorage }, impressionListener: impressionListener, debug: true, @@ -585,6 +586,13 @@ fullBrowserSettings.storage.type = 'MEMORY'; fullBrowserSettings.userConsent = 'DECLINED'; fullBrowserSettings.userConsent = 'UNKNOWN'; +const customStorage: SplitIO.StorageWrapper = { + getItem(key: string) { return Promise.resolve('value') }, + setItem(key: string, value: string) { return Promise.resolve() }, + removeItem(key: string) { return Promise.resolve() }, +} +fullBrowserSettings.storage.wrapper = customStorage; + let fullNodeSettings: SplitIO.INodeSettings = { core: { authorizationKey: 'asd', diff --git a/types/client/index.d.ts b/types/client/index.d.ts index d37238859..247bf7ef0 100644 --- a/types/client/index.d.ts +++ b/types/client/index.d.ts @@ -9,7 +9,7 @@ declare module JsSdk { /** * Split.io SDK factory function. * The settings parameter should be an object that complies with the SplitIO.IBrowserSettings. - * For more information read the corresponding article: @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#configuration} + * For more information read the corresponding article: @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/javascript-sdk/#configuration} */ export function SplitFactory(settings: SplitIO.IBrowserSettings): SplitIO.IBrowserSDK; } diff --git a/types/index.d.ts b/types/index.d.ts index 1d2c64470..3a36630fa 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -10,19 +10,19 @@ declare module JsSdk { /** * Split.io SDK factory function. * The settings parameter should be an object that complies with the SplitIO.INodeAsyncSettings. - * For more information read the corresponding article: @see {@link https://help.split.io/hc/en-us/articles/360020564931-Node-js-SDK#configuration} + * For more information read the corresponding article: @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/server-side-sdks/nodejs-sdk/#configuration} */ export function SplitFactory(settings: SplitIO.INodeAsyncSettings): SplitIO.IAsyncSDK; /** * Split.io SDK factory function. * The settings parameter should be an object that complies with the SplitIO.INodeSettings. - * For more information read the corresponding article: @see {@link https://help.split.io/hc/en-us/articles/360020564931-Node-js-SDK#configuration} + * For more information read the corresponding article: @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/server-side-sdks/nodejs-sdk/#configuration} */ export function SplitFactory(settings: SplitIO.INodeSettings): SplitIO.ISDK; /** * Split.io SDK factory function. * The settings parameter should be an object that complies with the SplitIO.IBrowserSettings. - * For more information read the corresponding article: @see {@link https://help.split.io/hc/en-us/articles/360020448791-JavaScript-SDK#configuration} + * For more information read the corresponding article: @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/client-side-sdks/javascript-sdk/#configuration} */ export function SplitFactory(settings: SplitIO.IBrowserSettings): SplitIO.IBrowserSDK; } diff --git a/types/server/index.d.ts b/types/server/index.d.ts index 35514aaa1..6752f5164 100644 --- a/types/server/index.d.ts +++ b/types/server/index.d.ts @@ -9,13 +9,13 @@ declare module JsSdk { /** * Split.io SDK factory function. * The settings parameter should be an object that complies with the SplitIO.INodeAsyncSettings. - * For more information read the corresponding article: @see {@link https://help.split.io/hc/en-us/articles/360020564931-Node-js-SDK#configuration} + * For more information read the corresponding article: @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/server-side-sdks/nodejs-sdk/#configuration} */ export function SplitFactory(settings: SplitIO.INodeAsyncSettings): SplitIO.IAsyncSDK; /** * Split.io SDK factory function. * The settings parameter should be an object that complies with the SplitIO.INodeSettings. - * For more information read the corresponding article: @see {@link https://help.split.io/hc/en-us/articles/360020564931-Node-js-SDK#configuration} + * For more information read the corresponding article: @see {@link https://developer.harness.io/docs/feature-management-experimentation/sdks-and-infrastructure/server-side-sdks/nodejs-sdk/#configuration} */ export function SplitFactory(settings: SplitIO.INodeSettings): SplitIO.ISDK; }