Skip to content

Commit 5a9a36a

Browse files
Update LocalStorage to clear storage if either SDK key or filter criteria changes
1 parent f27bd40 commit 5a9a36a

6 files changed

Lines changed: 42 additions & 25 deletions

File tree

CHANGES.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
0.13.0 (December XX, 2023)
22
- Added support for Flag Sets in "consumer" and "partial consumer" modes (pluggable storage).
3-
- Updated @splitsoftware/splitio-commons package to version 1.12.0 that includes flag sets support for "consumer" and "partial consumer" modes, and other improvements.
3+
- Updated SDK cache for browsers using localStorage, to clear cached feature flag definitions before initiating the synchronization process, if the cache was previously synchronized with a different SDK key (i.e., a different environment) or different Split Filter criteria.
4+
- Updated @splitsoftware/splitio-commons package to version 1.12.1 that includes flag sets support for "consumer" and "partial consumer" modes, and other improvements.
45
- Bugfixing - Fixed manager methods in consumer modes to return results in a promise when the SDK is not operational (not ready or destroyed).
56

67
0.12.0 (November 3, 2023)

package-lock.json

Lines changed: 7 additions & 7 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
@@ -64,7 +64,7 @@
6464
"bugs": "https://github.com/splitio/javascript-browser-client/issues",
6565
"homepage": "https://github.com/splitio/javascript-browser-client#readme",
6666
"dependencies": {
67-
"@splitsoftware/splitio-commons": "1.12.0",
67+
"@splitsoftware/splitio-commons": "1.12.1-rc.6",
6868
"@types/google.analytics": "0.0.40",
6969
"unfetch": "^4.2.0"
7070
},

src/__tests__/browserSuites/push-corner-cases.spec.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { getStorageHash } from '@splitsoftware/splitio-commons/src/storages/KeyBuilder';
12
import splitChangesMock1 from '../mocks/splitchanges.since.-1.json';
23
import splitKillMessage from '../mocks/message.SPLIT_KILL.1457552650000.json';
34
import authPushEnabledNicolas from '../mocks/auth.pushEnabled.nicolas@split.io.json';
@@ -46,6 +47,7 @@ export function testSplitKillOnReadyFromCache(fetchMock, assert) {
4647

4748
// prepare localstorage to allow SPLIT_KILL kill locally
4849
localStorage.clear();
50+
localStorage.setItem('pushCornerCase.SPLITIO.hash', getStorageHash(settings));
4951
localStorage.setItem('pushCornerCase.SPLITIO.splits.till', 25);
5052
localStorage.setItem('pushCornerCase.SPLITIO.split.whitelist', JSON.stringify({
5153
'name': 'whitelist',

src/__tests__/browserSuites/ready-from-cache.spec.js

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { getStorageHash } from '@splitsoftware/splitio-commons/src/storages/KeyBuilder';
12
import { SplitFactory, InLocalStorage } from '../../';
23

34
import splitChangesMock1 from '../mocks/splitchanges.since.-1.json';
@@ -82,6 +83,9 @@ const baseConfig = {
8283
streamingEnabled: false
8384
};
8485

86+
const expectedHashNullFilter = '9507ef4'; // for SDK key '<fake-token-rfc>' and filter query null
87+
const expectedHashWithFilter = '6a596ded'; // for SDK key '<fake-token-rfc>' and filter query '&names=p1__split,p2__split'
88+
8589
export default function (fetchMock, assert) {
8690

8791
assert.test(t => { // Testing when we start from scratch
@@ -102,7 +106,7 @@ export default function (fetchMock, assert) {
102106
...baseConfig,
103107
core: {
104108
...baseConfig.core,
105-
authorizationKey: '<fake-token-rfc2>',
109+
authorizationKey: '<fake-token-rfc>',
106110
},
107111
storage: InLocalStorage({
108112
prefix: 'readyFromCache_1'
@@ -134,7 +138,7 @@ export default function (fetchMock, assert) {
134138

135139
});
136140

137-
assert.test(t => { // Testing when we start with cached data but without lastUpdate item (previous version)
141+
assert.test(t => { // Testing when we start with cached data but without lastUpdate item (JS SDK version 10.13.0 and below)
138142
const testUrls = {
139143
sdk: 'https://sdk.baseurl/readyFromCacheWithData',
140144
events: 'https://events.baseurl/readyFromCacheWithData'
@@ -161,6 +165,7 @@ export default function (fetchMock, assert) {
161165
localStorage.setItem('some_user_item', 'user_item');
162166
localStorage.setItem('readyFromCache_2.SPLITIO.splits.till', 25);
163167
localStorage.setItem('readyFromCache_2.SPLITIO.split.always_on', alwaysOnSplitInverted);
168+
localStorage.setItem('readyFromCache_2.SPLITIO.hash', expectedHashNullFilter);
164169

165170
const startTime = Date.now();
166171
const splitio = SplitFactory({
@@ -270,6 +275,7 @@ export default function (fetchMock, assert) {
270275
localStorage.setItem('readyFromCache_3.SPLITIO.splits.till', 25);
271276
localStorage.setItem('readyFromCache_3.SPLITIO.splits.lastUpdated', Date.now());
272277
localStorage.setItem('readyFromCache_3.SPLITIO.split.always_on', alwaysOnSplitInverted);
278+
localStorage.setItem('readyFromCache_3.SPLITIO.hash', expectedHashNullFilter);
273279

274280
const startTime = Date.now();
275281
const splitio = SplitFactory({
@@ -386,6 +392,7 @@ export default function (fetchMock, assert) {
386392
localStorage.setItem('readyFromCache_4.SPLITIO.splits.till', 25);
387393
localStorage.setItem('readyFromCache_4.SPLITIO.splits.lastUpdated', Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS - 1); // -1 to ensure having an expired lastUpdated item
388394
localStorage.setItem('readyFromCache_4.SPLITIO.split.always_on', alwaysOnSplitInverted);
395+
localStorage.setItem('readyFromCache_4.SPLITIO.hash', expectedHashNullFilter);
389396

390397
const startTime = Date.now();
391398
const splitio = SplitFactory({
@@ -467,13 +474,13 @@ export default function (fetchMock, assert) {
467474

468475
/** Fetch specific splits **/
469476

470-
assert.test(t => { // Testing when we start with cached data without split filter, and a valid split filter config
477+
assert.test(t => { // Testing when we start with cached data but without storage hash (JS SDK <=v10.24.0 and Browser SDK <=v0.12.0), and a valid split filter config
471478
const testUrls = {
472479
sdk: 'https://sdk.baseurl/readyFromCache_5',
473480
events: 'https://events.baseurl/readyFromCache_5'
474481
};
475482
localStorage.clear();
476-
t.plan(6);
483+
t.plan(7);
477484

478485
fetchMock.getOnce(testUrls.sdk + '/splitChanges?since=-1&names=p1__split,p2__split', { status: 200, body: { splits: [splitDeclarations.p1__split, splitDeclarations.p2__split], since: -1, till: 1457552620999 } }, { delay: 10 }); // short delay to let emit SDK_READY_FROM_CACHE
479486
// fetchMock.getOnce(testUrls.sdk + '/splitChanges?since=1457552620999&names=p1__split', { status: 200, body: { splits: [], since: 1457552620999, till: 1457552620999 } });
@@ -483,6 +490,7 @@ export default function (fetchMock, assert) {
483490
localStorage.setItem('readyFromCache_5.SPLITIO.splits.till', 25);
484491
localStorage.setItem('readyFromCache_5.SPLITIO.split.p2__split', JSON.stringify(splitDeclarations.p2__split));
485492
localStorage.setItem('readyFromCache_5.SPLITIO.split.p3__split', JSON.stringify(splitDeclarations.p3__split));
493+
localStorage.setItem('readyFromCache_5.SPLITIO.splits.filterQuery', '&names=p2__split,p3__split'); // Deprecated item, should be cleaned
486494

487495
const splitio = SplitFactory({
488496
...baseConfig,
@@ -511,7 +519,8 @@ export default function (fetchMock, assert) {
511519
t.equal(localStorage.getItem('readyFromCache_5.SPLITIO.splits.till'), '1457552620999', 'splits.till must correspond to the till of the last successfully fetched Splits');
512520
t.equal(localStorage.getItem('readyFromCache_5.SPLITIO.split.p1__split'), JSON.stringify(splitDeclarations.p1__split), 'split declarations must be cached');
513521
t.equal(localStorage.getItem('readyFromCache_5.SPLITIO.split.p2__split'), JSON.stringify(splitDeclarations.p2__split), 'split declarations must be cached');
514-
t.equal(localStorage.getItem('readyFromCache_5.SPLITIO.splits.filterQuery'), '&names=p1__split,p2__split', 'splits.filterQuery must correspond to the split filter query');
522+
t.equal(localStorage.getItem('readyFromCache_5.SPLITIO.hash'), expectedHashWithFilter, 'Storage hash must correspond to the one for the SDK key and feature flag filter query');
523+
t.equal(localStorage.getItem('readyFromCache_5.SPLITIO.splits.filterQuery'), null);
515524
t.end();
516525
});
517526
});
@@ -554,13 +563,13 @@ export default function (fetchMock, assert) {
554563
t.equal(localStorage.getItem('readyFromCache_5B.SPLITIO.splits.till'), '1457552620999', 'splits.till must correspond to the till of the last successfully fetched Splits');
555564
t.equal(localStorage.getItem('readyFromCache_5B.SPLITIO.split.p1__split'), JSON.stringify(splitDeclarations.p1__split), 'split declarations must be cached');
556565
t.equal(localStorage.getItem('readyFromCache_5B.SPLITIO.split.p2__split'), JSON.stringify(splitDeclarations.p2__split), 'split declarations must be cached');
557-
t.equal(localStorage.getItem('readyFromCache_5B.SPLITIO.splits.filterQuery'), '&names=p1__split,p2__split', 'splits.filterQuery must correspond to the split filter query');
566+
t.equal(localStorage.getItem('readyFromCache_5B.SPLITIO.hash'), expectedHashWithFilter, 'Storage hash must correspond to the split filter query and SDK key');
558567
t.end();
559568
});
560569
});
561570
});
562571

563-
assert.test(t => { // Testing when we start with cached data with split filter, and the same split filter config
572+
assert.test(t => { // Testing when we start with cached data with split filter, and the same split filter is provided in the config
564573
const testUrls = {
565574
sdk: 'https://sdk.baseurl/readyFromCache_6',
566575
events: 'https://events.baseurl/readyFromCache_6'
@@ -571,11 +580,13 @@ export default function (fetchMock, assert) {
571580
fetchMock.getOnce(testUrls.sdk + '/splitChanges?since=25&names=p2__split&prefixes=p1', { status: 200, body: { splits: [splitDeclarations.p1__split, splitDeclarations.p2__split], since: 25, till: 1457552620999 } }, { delay: 10 }); // short delay to let emit SDK_READY_FROM_CACHE
572581
fetchMock.getOnce(testUrls.sdk + '/mySegments/nicolas%40split.io', { status: 200, body: { mySegments: [] } });
573582

583+
const expectedHash = getStorageHash({ ...baseConfig, sync: { __splitFiltersValidation: { queryString: '&names=p2__split&prefixes=p1' } } });
574584
localStorage.setItem('some_user_item', 'user_item');
575585
localStorage.setItem('readyFromCache_6.SPLITIO.splits.till', 25);
576586
localStorage.setItem('readyFromCache_6.SPLITIO.split.p1__split', JSON.stringify(splitDeclarations.p1__split));
577587
localStorage.setItem('readyFromCache_6.SPLITIO.split.p2__split', JSON.stringify(splitDeclarations.p2__split));
578588
localStorage.setItem('readyFromCache_6.SPLITIO.splits.filterQuery', '&names=p2__split&prefixes=p1');
589+
localStorage.setItem('readyFromCache_6.SPLITIO.hash', expectedHash);
579590

580591
const splitio = SplitFactory({
581592
...baseConfig,
@@ -603,13 +614,13 @@ export default function (fetchMock, assert) {
603614
t.equal(localStorage.getItem('readyFromCache_6.SPLITIO.splits.till'), '1457552620999', 'splits.till must correspond to the till of the last successfully fetched Splits');
604615
t.equal(localStorage.getItem('readyFromCache_6.SPLITIO.split.p1__split'), JSON.stringify(splitDeclarations.p1__split), 'split declarations must be cached');
605616
t.equal(localStorage.getItem('readyFromCache_6.SPLITIO.split.p2__split'), JSON.stringify(splitDeclarations.p2__split), 'split declarations must be cached');
606-
t.equal(localStorage.getItem('readyFromCache_6.SPLITIO.splits.filterQuery'), '&names=p2__split&prefixes=p1', 'splits.filterQuery must correspond to the split filter query');
617+
t.equal(localStorage.getItem('readyFromCache_6.SPLITIO.hash'), expectedHash, 'Storage hash must correspond to the split filter query and SDK key');
607618
t.end();
608619
});
609620
});
610621
});
611622

612-
assert.test(t => { // Testing when we start with cached data with split filter but expired, and the same split filter config
623+
assert.test(t => { // Testing when we start with cached data with split filter but expired, and the same split filter is provided in the config
613624
const testUrls = {
614625
sdk: 'https://sdk.baseurl/readyFromCache_7',
615626
events: 'https://events.baseurl/readyFromCache_7'
@@ -620,11 +631,12 @@ export default function (fetchMock, assert) {
620631
fetchMock.getOnce(testUrls.sdk + '/splitChanges?since=-1&prefixes=p1,p2', { status: 200, body: { splits: [splitDeclarations.p1__split, splitDeclarations.p2__split], since: -1, till: 1457552620999 } }, { delay: 10 }); // short delay to let emit SDK_READY_FROM_CACHE
621632
fetchMock.getOnce(testUrls.sdk + '/mySegments/nicolas%40split.io', { status: 200, body: { mySegments: [] } });
622633

634+
const expectedHash = getStorageHash({ ...baseConfig, sync: { __splitFiltersValidation: { queryString: '&prefixes=p1,p2' } } });
623635
localStorage.setItem('some_user_item', 'user_item');
624636
localStorage.setItem('readyFromCache_7.SPLITIO.splits.till', 25);
625637
localStorage.setItem('readyFromCache_7.SPLITIO.split.p1__split', JSON.stringify(splitDeclarations.p1__split));
626638
localStorage.setItem('readyFromCache_7.SPLITIO.split.p2__split', JSON.stringify(splitDeclarations.p2__split));
627-
localStorage.setItem('readyFromCache_7.SPLITIO.splits.filterQuery', '&prefixes=p1,p2');
639+
localStorage.setItem('readyFromCache_7.SPLITIO.hash', expectedHash);
628640
localStorage.setItem('readyFromCache_7.SPLITIO.splits.lastUpdated', Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS - 1); // -1 to ensure having an expired lastUpdated item
629641

630642
const splitio = SplitFactory({
@@ -654,7 +666,7 @@ export default function (fetchMock, assert) {
654666
t.equal(localStorage.getItem('readyFromCache_7.SPLITIO.splits.till'), '1457552620999', 'splits.till must correspond to the till of the last successfully fetched Splits');
655667
t.equal(localStorage.getItem('readyFromCache_7.SPLITIO.split.p1__split'), JSON.stringify(splitDeclarations.p1__split), 'split declarations must be cached');
656668
t.equal(localStorage.getItem('readyFromCache_7.SPLITIO.split.p2__split'), JSON.stringify(splitDeclarations.p2__split), 'split declarations must be cached');
657-
t.equal(localStorage.getItem('readyFromCache_7.SPLITIO.splits.filterQuery'), '&prefixes=p1,p2', 'splits.filterQuery must correspond to the split filter query');
669+
t.equal(localStorage.getItem('readyFromCache_7.SPLITIO.hash'), expectedHash, 'Storage hash must correspond to the split filter query and SDK key');
658670
t.end();
659671
});
660672
});
@@ -689,7 +701,7 @@ export default function (fetchMock, assert) {
689701
localStorage.setItem('readyFromCache_8.SPLITIO.split.p1__split', JSON.stringify(splitDeclarations.p1__split));
690702
localStorage.setItem('readyFromCache_8.SPLITIO.split.p2__split', JSON.stringify(splitDeclarations.p2__split));
691703
localStorage.setItem('readyFromCache_8.SPLITIO.split.deleted__split', '{ "name": "deleted_split" }');
692-
localStorage.setItem('readyFromCache_8.SPLITIO.splits.filterQuery', '&names=p2__split&prefixes=p1');
704+
localStorage.setItem('readyFromCache_8.SPLITIO.hash', expectedHashWithFilter);
693705

694706
const splitio = SplitFactory({
695707
...baseConfig,
@@ -717,7 +729,7 @@ export default function (fetchMock, assert) {
717729
t.equal(localStorage.getItem('readyFromCache_8.SPLITIO.split.p1__split'), JSON.stringify(splitDeclarations.p1__split), 'split declarations must be cached');
718730
t.equal(localStorage.getItem('readyFromCache_8.SPLITIO.split.p2__split'), JSON.stringify(splitDeclarations.p2__split), 'split declarations must be cached');
719731
t.equal(localStorage.getItem('readyFromCache_8.SPLITIO.split.p3__split'), JSON.stringify(splitDeclarations.p3__split), 'split declarations must be cached');
720-
t.equal(localStorage.getItem('readyFromCache_8.SPLITIO.splits.filterQuery'), null, 'splits.filterQuery must correspond to the split filter query');
732+
t.equal(localStorage.getItem('readyFromCache_8.SPLITIO.hash'), expectedHashNullFilter, 'Storage hash must correspond to the split filter query and SDK key');
721733
t.end();
722734
});
723735
});
@@ -726,7 +738,7 @@ export default function (fetchMock, assert) {
726738
});
727739
});
728740

729-
assert.test(t => { // Testing when we start with cached data with split filter, and a new valid split filter config
741+
assert.test(t => { // Testing when we start with cached data with split filter, and a new split filter is provided in the config
730742
const testUrls = {
731743
sdk: 'https://sdk.baseurl/readyFromCache_9',
732744
events: 'https://events.baseurl/readyFromCache_9'
@@ -741,7 +753,7 @@ export default function (fetchMock, assert) {
741753
localStorage.setItem('readyFromCache_9.SPLITIO.splits.till', 25);
742754
localStorage.setItem('readyFromCache_9.SPLITIO.split.p1__split', JSON.stringify(splitDeclarations.p1__split));
743755
localStorage.setItem('readyFromCache_9.SPLITIO.split.p2__split', JSON.stringify(splitDeclarations.p2__split));
744-
localStorage.setItem('readyFromCache_9.SPLITIO.splits.filterQuery', '&names=p2__split&prefixes=p1');
756+
localStorage.setItem('readyFromCache_9.SPLITIO.hash', getStorageHash({ ...baseConfig, sync: { __splitFiltersValidation: { queryString: '&names=p2__split&prefixes=p1' } } }));
745757

746758
const splitio = SplitFactory({
747759
...baseConfig,
@@ -770,7 +782,7 @@ export default function (fetchMock, assert) {
770782
t.equal(localStorage.getItem('readyFromCache_9.SPLITIO.splits.till'), '1457552620999', 'splits.till must correspond to the till of the last successfully fetched Splits');
771783
t.equal(localStorage.getItem('readyFromCache_9.SPLITIO.split.p2__split'), JSON.stringify(splitDeclarations.p2__split), 'split declarations must be cached');
772784
t.equal(localStorage.getItem('readyFromCache_9.SPLITIO.split.p3__split'), JSON.stringify(splitDeclarations.p3__split), 'split declarations must be cached');
773-
t.equal(localStorage.getItem('readyFromCache_9.SPLITIO.splits.filterQuery'), '&names=no%20exist%20trim,no_exist,p3__split&prefixes=no%20exist%20trim,p2', 'splits.filterQuery must correspond to the split filter query');
785+
t.equal(localStorage.getItem('readyFromCache_9.SPLITIO.hash'), getStorageHash({ ...baseConfig, sync: { __splitFiltersValidation: { queryString: '&names=no%20exist%20trim,no_exist,p3__split&prefixes=no%20exist%20trim,p2' } } }), 'Storage hash must correspond to the split filter query and SDK key');
774786
t.end();
775787
});
776788
});

src/__tests__/errorCatching/browser.spec.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// Here we are testing exceptions and the handler should be ours, we need to avoid tape-catch
2+
import { getStorageHash } from '@splitsoftware/splitio-commons/src/storages/KeyBuilder';
23
import tape from 'tape';
34
import includes from 'lodash/includes';
45
import fetchMock from '../testUtils/fetchMock';
@@ -20,6 +21,7 @@ const settings = settingsFactory({
2021
// prepare localstorage to emit SDK_READY_FROM_CACHE
2122
localStorage.clear();
2223
localStorage.setItem('SPLITIO.splits.till', 25);
24+
localStorage.setItem('SPLITIO.hash', getStorageHash({ core: { authorizationKey: '<fake-token-1>' }, sync: { __splitFiltersValidation: { queryString: null } } }));
2325

2426
fetchMock.get(url(settings, '/splitChanges?since=25'), function () {
2527
return new Promise((res) => { setTimeout(() => res({ status: 200, body: splitChangesMock1 }), 1000); });

0 commit comments

Comments
 (0)