Skip to content

Commit b16f009

Browse files
Merge pull request #284 from splitio/update_localstorage_clear
Update LocalStorage to clear storage if SDK key or filter criteria change
2 parents 64f017c + 086c08e commit b16f009

File tree

7 files changed

+42
-36
lines changed

7 files changed

+42
-36
lines changed

CHANGES.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
1.12.1 (December 12, 2023)
2-
- Updated PluggableStorage for producer mode, to clear the storage if it was previously synchronized with a different SDK key (i.e., a different environment) or different Split Filter criteria.
2+
- Updated PluggableStorage, for producer mode, and LocalStorage, for standalone mode, to clear the storage before initiating the synchronization process if it was previously synchronized with a different SDK key (i.e., a different environment) or different Split Filter criteria.
33
- Bugfixing - Fixed an issue when tracking telemetry latencies for the new `getTreatmentsByFlagSet(s)` methods in Redis and Pluggable storages, which was causing the SDK to not track those stats.
44

55
1.12.0 (December 4, 2023)

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": "1.12.0",
3+
"version": "1.12.1",
44
"description": "Split Javascript SDK common components",
55
"main": "cjs/index.js",
66
"module": "esm/index.js",

src/storages/KeyBuilderCS.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,4 @@ export class KeyBuilderCS extends KeyBuilder {
4545
isSplitsCacheKey(key: string) {
4646
return this.regexSplitsCacheKey.test(key);
4747
}
48-
49-
buildSplitsFilterQueryKey() {
50-
return `${this.prefix}.splits.filterQuery`;
51-
}
5248
}

src/storages/inLocalStorage/SplitsCacheInLocal.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
1-
import { ISplit, ISplitFiltersValidation } from '../../dtos/types';
1+
import { ISplit } from '../../dtos/types';
22
import { AbstractSplitsCacheSync, usesSegments } from '../AbstractSplitsCacheSync';
33
import { isFiniteNumber, toNumber, isNaNNumber } from '../../utils/lang';
44
import { KeyBuilderCS } from '../KeyBuilderCS';
55
import { ILogger } from '../../logger/types';
66
import { LOG_PREFIX } from './constants';
77
import { ISet, _Set, setToArray } from '../../utils/lang/sets';
8+
import { ISettings } from '../../types';
9+
import { getStorageHash } from '../KeyBuilder';
810

911
/**
1012
* ISplitsCacheSync implementation that stores split definitions in browser LocalStorage.
1113
*/
1214
export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
1315

1416
private readonly keys: KeyBuilderCS;
15-
private readonly splitFiltersValidation: ISplitFiltersValidation;
17+
private readonly log: ILogger;
18+
private readonly storageHash: string;
1619
private readonly flagSetsFilter: string[];
1720
private hasSync?: boolean;
1821
private updateNewFilter?: boolean;
@@ -22,11 +25,12 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
2225
* @param {number | undefined} expirationTimestamp
2326
* @param {ISplitFiltersValidation} splitFiltersValidation
2427
*/
25-
constructor(private readonly log: ILogger, keys: KeyBuilderCS, expirationTimestamp?: number, splitFiltersValidation: ISplitFiltersValidation = { queryString: null, groupedFilters: { bySet: [], byName: [], byPrefix: [] }, validFilters: [] }) {
28+
constructor(settings: ISettings, keys: KeyBuilderCS, expirationTimestamp?: number) {
2629
super();
2730
this.keys = keys;
28-
this.splitFiltersValidation = splitFiltersValidation;
29-
this.flagSetsFilter = this.splitFiltersValidation.groupedFilters.bySet;
31+
this.log = settings.log;
32+
this.storageHash = getStorageHash(settings);
33+
this.flagSetsFilter = settings.sync.__splitFiltersValidation.groupedFilters.bySet;
3034

3135
this._checkExpiration(expirationTimestamp);
3236

@@ -142,12 +146,10 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
142146

143147
// when using a new split query, we must update it at the store
144148
if (this.updateNewFilter) {
145-
this.log.info(LOG_PREFIX + 'Split filter query was modified. Updating cache.');
146-
const queryKey = this.keys.buildSplitsFilterQueryKey();
147-
const queryString = this.splitFiltersValidation.queryString;
149+
this.log.info(LOG_PREFIX + 'SDK key or feature flag filter criteria was modified. Updating cache');
150+
const storageHashKey = this.keys.buildHashKey();
148151
try {
149-
if (queryString) localStorage.setItem(queryKey, queryString);
150-
else localStorage.removeItem(queryKey);
152+
localStorage.setItem(storageHashKey, this.storageHash);
151153
} catch (e) {
152154
this.log.error(LOG_PREFIX + e);
153155
}
@@ -237,12 +239,12 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync {
237239
}
238240
}
239241

242+
// @TODO eventually remove `_checkFilterQuery`. Cache should be cleared at the storage level, reusing same logic than PluggableStorage
240243
private _checkFilterQuery() {
241-
const queryString = this.splitFiltersValidation.queryString;
242-
const queryKey = this.keys.buildSplitsFilterQueryKey();
243-
const currentQueryString = localStorage.getItem(queryKey);
244+
const storageHashKey = this.keys.buildHashKey();
245+
const storageHash = localStorage.getItem(storageHashKey);
244246

245-
if (currentQueryString !== queryString) {
247+
if (storageHash !== this.storageHash) {
246248
try {
247249
// mark cache to update the new query filter on first successful splits fetch
248250
this.updateNewFilter = true;

src/storages/inLocalStorage/__tests__/SplitsCacheInLocal.spec.ts

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { SplitsCacheInLocal } from '../SplitsCacheInLocal';
22
import { KeyBuilderCS } from '../../KeyBuilderCS';
3-
import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock';
43
import { splitWithUserTT, splitWithAccountTT, splitWithAccountTTAndUsesSegments, something, somethingElse, featureFlagOne, featureFlagTwo, featureFlagThree, featureFlagWithEmptyFS, featureFlagWithoutFS } from '../../__tests__/testUtils';
54
import { ISplit } from '../../../dtos/types';
65
import { _Set } from '../../../utils/lang/sets';
6+
import { fullSettings } from '../../../utils/settingsValidation/__tests__/settings.mocks';
7+
78

89
test('SPLIT CACHE / LocalStorage', () => {
9-
const cache = new SplitsCacheInLocal(loggerMock, new KeyBuilderCS('SPLITIO', 'user'));
10+
const cache = new SplitsCacheInLocal(fullSettings, new KeyBuilderCS('SPLITIO', 'user'));
1011

1112
cache.clear();
1213

@@ -45,7 +46,7 @@ test('SPLIT CACHE / LocalStorage', () => {
4546
});
4647

4748
test('SPLIT CACHE / LocalStorage / Get Keys', () => {
48-
const cache = new SplitsCacheInLocal(loggerMock, new KeyBuilderCS('SPLITIO', 'user'));
49+
const cache = new SplitsCacheInLocal(fullSettings, new KeyBuilderCS('SPLITIO', 'user'));
4950

5051
cache.addSplit('lol1', something);
5152
cache.addSplit('lol2', somethingElse);
@@ -57,7 +58,7 @@ test('SPLIT CACHE / LocalStorage / Get Keys', () => {
5758
});
5859

5960
test('SPLIT CACHE / LocalStorage / Add Splits', () => {
60-
const cache = new SplitsCacheInLocal(loggerMock, new KeyBuilderCS('SPLITIO', 'user'));
61+
const cache = new SplitsCacheInLocal(fullSettings, new KeyBuilderCS('SPLITIO', 'user'));
6162

6263
cache.addSplits([
6364
['lol1', something],
@@ -71,7 +72,7 @@ test('SPLIT CACHE / LocalStorage / Add Splits', () => {
7172
});
7273

7374
test('SPLIT CACHE / LocalStorage / trafficTypeExists and ttcache tests', () => {
74-
const cache = new SplitsCacheInLocal(loggerMock, new KeyBuilderCS('SPLITIO', 'user'));
75+
const cache = new SplitsCacheInLocal(fullSettings, new KeyBuilderCS('SPLITIO', 'user'));
7576

7677
cache.addSplits([ // loop of addSplit
7778
['split1', splitWithUserTT],
@@ -109,7 +110,7 @@ test('SPLIT CACHE / LocalStorage / trafficTypeExists and ttcache tests', () => {
109110
});
110111

111112
test('SPLIT CACHE / LocalStorage / killLocally', () => {
112-
const cache = new SplitsCacheInLocal(loggerMock, new KeyBuilderCS('SPLITIO', 'user'));
113+
const cache = new SplitsCacheInLocal(fullSettings, new KeyBuilderCS('SPLITIO', 'user'));
113114
cache.addSplit('lol1', something);
114115
cache.addSplit('lol2', somethingElse);
115116
const initialChangeNumber = cache.getChangeNumber();
@@ -141,7 +142,7 @@ test('SPLIT CACHE / LocalStorage / killLocally', () => {
141142
});
142143

143144
test('SPLIT CACHE / LocalStorage / usesSegments', () => {
144-
const cache = new SplitsCacheInLocal(loggerMock, new KeyBuilderCS('SPLITIO', 'user'));
145+
const cache = new SplitsCacheInLocal(fullSettings, new KeyBuilderCS('SPLITIO', 'user'));
145146

146147
expect(cache.usesSegments()).toBe(true); // true initially, until data is synchronized
147148
cache.setChangeNumber(1); // to indicate that data has been synced.
@@ -164,7 +165,15 @@ test('SPLIT CACHE / LocalStorage / usesSegments', () => {
164165

165166
test('SPLIT CACHE / LocalStorage / flag set cache tests', () => {
166167
// @ts-ignore
167-
const cache = new SplitsCacheInLocal(loggerMock, new KeyBuilderCS('SPLITIO', 'user'), undefined, { groupedFilters: { bySet: ['o', 'n', 'e', 'x'] } });
168+
const cache = new SplitsCacheInLocal({
169+
...fullSettings,
170+
sync: { // @ts-expect-error
171+
__splitFiltersValidation: {
172+
groupedFilters: { bySet: ['o', 'n', 'e', 'x'], byName: [], byPrefix: [] },
173+
queryString: '&sets=e,n,o,x',
174+
}
175+
}
176+
}, new KeyBuilderCS('SPLITIO', 'user'));
168177
const emptySet = new _Set([]);
169178

170179
cache.addSplits([
@@ -204,7 +213,7 @@ test('SPLIT CACHE / LocalStorage / flag set cache tests', () => {
204213

205214
// if FlagSets are not defined, it should store all FlagSets in memory.
206215
test('SPLIT CACHE / LocalStorage / flag set cache tests without filters', () => {
207-
const cacheWithoutFilters = new SplitsCacheInLocal(loggerMock, new KeyBuilderCS('SPLITIO', 'user'));
216+
const cacheWithoutFilters = new SplitsCacheInLocal(fullSettings, new KeyBuilderCS('SPLITIO', 'user'));
208217
const emptySet = new _Set([]);
209218

210219
cacheWithoutFilters.addSplits([
@@ -221,9 +230,8 @@ test('SPLIT CACHE / LocalStorage / flag set cache tests without filters', () =>
221230
expect(cacheWithoutFilters.getNamesByFlagSets(['y'])).toEqual([emptySet]);
222231
expect(cacheWithoutFilters.getNamesByFlagSets(['o', 'n', 'e'])).toEqual([new _Set(['ff_one', 'ff_two']), new _Set(['ff_one']), new _Set(['ff_one', 'ff_three'])]);
223232

224-
// Validate that the cache is cleared when calling `clear` method
225-
localStorage.setItem('something', 'something');
233+
// Validate that the feature flag cache is cleared when calling `clear` method
226234
cacheWithoutFilters.clear();
227-
expect(localStorage.length).toBe(1); // only 'something' should remain in localStorage
228-
expect(localStorage.getItem(localStorage.key(0)!)).toBe('something');
235+
expect(localStorage.length).toBe(1); // only 'SPLITIO.hash' should remain in localStorage
236+
expect(localStorage.key(0)).toBe('SPLITIO.hash');
229237
});

src/storages/inLocalStorage/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export function InLocalStorage(options: InLocalStorageOptions = {}): IStorageSyn
4141
const keys = new KeyBuilderCS(prefix, matchingKey as string);
4242
const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS;
4343

44-
const splits = new SplitsCacheInLocal(log, keys, expirationTimestamp, __splitFiltersValidation);
44+
const splits = new SplitsCacheInLocal(settings, keys, expirationTimestamp);
4545
const segments = new MySegmentsCacheInLocal(log, keys);
4646

4747
return {

0 commit comments

Comments
 (0)