From ec15d2ccc79d16ea902dad3b5d0ba692a57dd79f Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Sun, 2 Nov 2025 17:46:55 -0300 Subject: [PATCH 1/5] fix: update import path for LOG_PREFIX constant in Redis storage --- src/readiness/__tests__/readinessManager.spec.ts | 4 ---- src/storages/inLocalStorage/__tests__/validateCache.spec.ts | 5 +++++ src/storages/inRedis/SegmentsCacheInRedis.ts | 2 +- types/splitio.d.ts | 2 -- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/readiness/__tests__/readinessManager.spec.ts b/src/readiness/__tests__/readinessManager.spec.ts index 174f1373..34eaf9a3 100644 --- a/src/readiness/__tests__/readinessManager.spec.ts +++ b/src/readiness/__tests__/readinessManager.spec.ts @@ -3,14 +3,10 @@ import { EventEmitter } from '../../utils/MinEvents'; import { IReadinessManager } from '../types'; import { SDK_READY, SDK_UPDATE, SDK_SPLITS_ARRIVED, SDK_SEGMENTS_ARRIVED, SDK_READY_FROM_CACHE, SDK_SPLITS_CACHE_LOADED, SDK_READY_TIMED_OUT } from '../constants'; import { ISettings } from '../../types'; -import { STORAGE_LOCALSTORAGE } from '../../utils/constants'; const settings = { startup: { readyTimeout: 0, - }, - storage: { - type: STORAGE_LOCALSTORAGE } } as unknown as ISettings; diff --git a/src/storages/inLocalStorage/__tests__/validateCache.spec.ts b/src/storages/inLocalStorage/__tests__/validateCache.spec.ts index 102444ba..7feea541 100644 --- a/src/storages/inLocalStorage/__tests__/validateCache.spec.ts +++ b/src/storages/inLocalStorage/__tests__/validateCache.spec.ts @@ -47,6 +47,7 @@ describe.each(storages)('validateCache', (storage) => { test('if there is cache and it must not be cleared, it should return true', async () => { storage.setItem(keys.buildSplitsTillKey(), '1'); storage.setItem(keys.buildHashKey(), FULL_SETTINGS_HASH); + await storage.save && storage.save(); expect(await validateCache({}, storage, fullSettings, keys, splits, rbSegments, segments, largeSegments)).toBe(true); @@ -66,6 +67,7 @@ describe.each(storages)('validateCache', (storage) => { storage.setItem(keys.buildSplitsTillKey(), '1'); storage.setItem(keys.buildHashKey(), FULL_SETTINGS_HASH); storage.setItem(keys.buildLastUpdatedKey(), Date.now() - 1000 * 60 * 60 * 24 * 2 + ''); // 2 days ago + await storage.save && storage.save(); expect(await validateCache({ expirationDays: 1 }, storage, fullSettings, keys, splits, rbSegments, segments, largeSegments)).toBe(false); @@ -83,6 +85,7 @@ describe.each(storages)('validateCache', (storage) => { test('if there is cache and its hash has changed, it should clear cache and return false', async () => { storage.setItem(keys.buildSplitsTillKey(), '1'); storage.setItem(keys.buildHashKey(), FULL_SETTINGS_HASH); + await storage.save && storage.save(); expect(await validateCache({}, storage, { ...fullSettings, core: { ...fullSettings.core, authorizationKey: 'another-sdk-key' } }, keys, splits, rbSegments, segments, largeSegments)).toBe(false); @@ -99,8 +102,10 @@ describe.each(storages)('validateCache', (storage) => { test('if there is cache and clearOnInit is true, it should clear cache and return false', async () => { // Older cache version (without last clear) + storage.removeItem(keys.buildLastClear()); storage.setItem(keys.buildSplitsTillKey(), '1'); storage.setItem(keys.buildHashKey(), FULL_SETTINGS_HASH); + await storage.save && storage.save(); expect(await validateCache({ clearOnInit: true }, storage, fullSettings, keys, splits, rbSegments, segments, largeSegments)).toBe(false); diff --git a/src/storages/inRedis/SegmentsCacheInRedis.ts b/src/storages/inRedis/SegmentsCacheInRedis.ts index d645495f..5a81dede 100644 --- a/src/storages/inRedis/SegmentsCacheInRedis.ts +++ b/src/storages/inRedis/SegmentsCacheInRedis.ts @@ -1,6 +1,6 @@ import { ILogger } from '../../logger/types'; import { isNaNNumber } from '../../utils/lang'; -import { LOG_PREFIX } from '../inLocalStorage/constants'; +import { LOG_PREFIX } from './constants'; import { KeyBuilderSS } from '../KeyBuilderSS'; import { ISegmentsCacheAsync } from '../types'; import type { RedisAdapter } from './RedisAdapter'; diff --git a/types/splitio.d.ts b/types/splitio.d.ts index bd7da67c..ba2be58b 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -1335,8 +1335,6 @@ declare namespace SplitIO { /** * Defines the factory function to instantiate the storage. If not provided, the default in-memory storage is used. * - * NOTE: Currently, there is no persistent storage option available for the React Native SDK; only `InLocalStorage` for the Browser SDK. - * * Example: * ``` * SplitFactory({ From d35663b0f4d41e79d74889cb15439d80edbce118 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 6 Nov 2025 15:05:43 -0300 Subject: [PATCH 2/5] perf: avoid redundant setItem call if flagSetCache has not changed --- src/storages/inLocalStorage/SplitsCacheInLocal.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/storages/inLocalStorage/SplitsCacheInLocal.ts b/src/storages/inLocalStorage/SplitsCacheInLocal.ts index 3aa08452..51c8c485 100644 --- a/src/storages/inLocalStorage/SplitsCacheInLocal.ts +++ b/src/storages/inLocalStorage/SplitsCacheInLocal.ts @@ -206,6 +206,9 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync { const flagSetFromStorage = this.storage.getItem(flagSetKey); const flagSetCache = new Set(flagSetFromStorage ? JSON.parse(flagSetFromStorage) : []); + + if (flagSetCache.has(featureFlag.name)) return; + flagSetCache.add(featureFlag.name); this.storage.setItem(flagSetKey, JSON.stringify(setToArray(flagSetCache))); From ba83154b0a4ad9ee820b824df3971638143a9339 Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Wed, 19 Nov 2025 16:59:14 -0300 Subject: [PATCH 3/5] refactor: set change number only if update operations don't fail, for storage consistency --- CHANGES.txt | 3 ++ src/storages/AbstractMySegmentsCacheSync.ts | 46 ++++++++++-------- src/storages/AbstractSplitsCacheSync.ts | 5 +- .../inLocalStorage/MySegmentsCacheInLocal.ts | 39 +++++++-------- .../inLocalStorage/RBSegmentsCacheInLocal.ts | 45 +++++++---------- .../inLocalStorage/SplitsCacheInLocal.ts | 48 ++++++++----------- .../inMemory/RBSegmentsCacheInMemory.ts | 5 +- .../polling/updaters/splitChangesUpdater.ts | 4 +- 8 files changed, 91 insertions(+), 104 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 4c71ee9f..e66a3f50 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,6 @@ +2.8.1 (November 21, 2025) + - Updated the order of storage operations to prevent inconsistent states when using the `LOCALSTORAGE` storage type and the browser’s `localStorage` fails due to quota limits. + 2.8.0 (October 30, 2025) - Added new configuration for Fallback Treatments, which allows setting a treatment value and optional config to be returned in place of "control", either globally or by flag. Read more in our docs. - Added `client.getStatus()` method to retrieve the client readiness status properties (`isReady`, `isReadyFromCache`, etc). diff --git a/src/storages/AbstractMySegmentsCacheSync.ts b/src/storages/AbstractMySegmentsCacheSync.ts index 7d3dc304..289fdc0d 100644 --- a/src/storages/AbstractMySegmentsCacheSync.ts +++ b/src/storages/AbstractMySegmentsCacheSync.ts @@ -49,12 +49,10 @@ export abstract class AbstractMySegmentsCacheSync implements ISegmentsCacheSync * For client-side synchronizer: it resets or updates the cache. */ resetSegments(segmentsData: MySegmentsData | IMySegmentsResponse): boolean { - this.setChangeNumber(segmentsData.cn); - + let isDiff = false; const { added, removed } = segmentsData as MySegmentsData; if (added && removed) { - let isDiff = false; added.forEach(segment => { isDiff = this.addSegment(segment) || isDiff; @@ -63,32 +61,40 @@ export abstract class AbstractMySegmentsCacheSync implements ISegmentsCacheSync removed.forEach(segment => { isDiff = this.removeSegment(segment) || isDiff; }); + } else { - return isDiff; - } + const names = ((segmentsData as IMySegmentsResponse).k || []).map(s => s.n).sort(); + const storedSegmentKeys = this.getRegisteredSegments().sort(); - const names = ((segmentsData as IMySegmentsResponse).k || []).map(s => s.n).sort(); - const storedSegmentKeys = this.getRegisteredSegments().sort(); + // Extreme fast => everything is empty + if (!names.length && !storedSegmentKeys.length) { + isDiff = false; + } else { - // Extreme fast => everything is empty - if (!names.length && !storedSegmentKeys.length) return false; + let index = 0; - let index = 0; + while (index < names.length && index < storedSegmentKeys.length && names[index] === storedSegmentKeys[index]) index++; - while (index < names.length && index < storedSegmentKeys.length && names[index] === storedSegmentKeys[index]) index++; + // Quick path => no changes + if (index === names.length && index === storedSegmentKeys.length) { + isDiff = false; + } else { - // Quick path => no changes - if (index === names.length && index === storedSegmentKeys.length) return false; + // Slowest path => add and/or remove segments + for (let removeIndex = index; removeIndex < storedSegmentKeys.length; removeIndex++) { + this.removeSegment(storedSegmentKeys[removeIndex]); + } - // Slowest path => add and/or remove segments - for (let removeIndex = index; removeIndex < storedSegmentKeys.length; removeIndex++) { - this.removeSegment(storedSegmentKeys[removeIndex]); - } + for (let addIndex = index; addIndex < names.length; addIndex++) { + this.addSegment(names[addIndex]); + } - for (let addIndex = index; addIndex < names.length; addIndex++) { - this.addSegment(names[addIndex]); + isDiff = true; + } + } } - return true; + this.setChangeNumber(segmentsData.cn); + return isDiff; } } diff --git a/src/storages/AbstractSplitsCacheSync.ts b/src/storages/AbstractSplitsCacheSync.ts index 2a4b9b78..64194561 100644 --- a/src/storages/AbstractSplitsCacheSync.ts +++ b/src/storages/AbstractSplitsCacheSync.ts @@ -14,9 +14,10 @@ export abstract class AbstractSplitsCacheSync implements ISplitsCacheSync { protected abstract setChangeNumber(changeNumber: number): boolean | void update(toAdd: ISplit[], toRemove: ISplit[], changeNumber: number): boolean { + let updated = toAdd.map(addedFF => this.addSplit(addedFF)).some(result => result); + updated = toRemove.map(removedFF => this.removeSplit(removedFF.name)).some(result => result) || updated; this.setChangeNumber(changeNumber); - const updated = toAdd.map(addedFF => this.addSplit(addedFF)).some(result => result); - return toRemove.map(removedFF => this.removeSplit(removedFF.name)).some(result => result) || updated; + return updated; } abstract getSplit(name: string): ISplit | null diff --git a/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts b/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts index fd038a07..1c54fdff 100644 --- a/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +++ b/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts @@ -22,27 +22,17 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync { protected addSegment(name: string): boolean { const segmentKey = this.keys.buildSegmentNameKey(name); - try { - if (this.storage.getItem(segmentKey) === DEFINED) return false; - this.storage.setItem(segmentKey, DEFINED); - return true; - } catch (e) { - this.log.error(LOG_PREFIX + e); - return false; - } + if (this.storage.getItem(segmentKey) === DEFINED) return false; + this.storage.setItem(segmentKey, DEFINED); + return true; } protected removeSegment(name: string): boolean { const segmentKey = this.keys.buildSegmentNameKey(name); - try { - if (this.storage.getItem(segmentKey) !== DEFINED) return false; - this.storage.removeItem(segmentKey); - return true; - } catch (e) { - this.log.error(LOG_PREFIX + e); - return false; - } + if (this.storage.getItem(segmentKey) !== DEFINED) return false; + this.storage.removeItem(segmentKey); + return true; } isInSegment(name: string): boolean { @@ -63,12 +53,8 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync { } protected setChangeNumber(changeNumber?: number) { - try { - if (changeNumber) this.storage.setItem(this.keys.buildTillKey(), changeNumber + ''); - else this.storage.removeItem(this.keys.buildTillKey()); - } catch (e) { - this.log.error(e); - } + if (changeNumber) this.storage.setItem(this.keys.buildTillKey(), changeNumber + ''); + else this.storage.removeItem(this.keys.buildTillKey()); } getChangeNumber() { @@ -84,4 +70,13 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync { return n; } + registerSegments() { + try { + return super.registerSegments(); + } catch (e) { + this.log.error(LOG_PREFIX + e); + return false; + } + } + } diff --git a/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts b/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts index 56e8ed41..61bf13fa 100644 --- a/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts +++ b/src/storages/inLocalStorage/RBSegmentsCacheInLocal.ts @@ -26,9 +26,10 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync { } update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): boolean { + let updated = toAdd.map(toAdd => this.add(toAdd)).some(result => result); + updated = toRemove.map(toRemove => this.remove(toRemove.name)).some(result => result) || updated; this.setChangeNumber(changeNumber); - const updated = toAdd.map(toAdd => this.add(toAdd)).some(result => result); - return toRemove.map(toRemove => this.remove(toRemove.name)).some(result => result) || updated; + return updated; } private setChangeNumber(changeNumber: number) { @@ -48,40 +49,30 @@ export class RBSegmentsCacheInLocal implements IRBSegmentsCacheSync { } private add(rbSegment: IRBSegment): boolean { - try { - const name = rbSegment.name; - const rbSegmentKey = this.keys.buildRBSegmentKey(name); - const rbSegmentFromStorage = this.storage.getItem(rbSegmentKey); - const previous = rbSegmentFromStorage ? JSON.parse(rbSegmentFromStorage) : null; + const name = rbSegment.name; + const rbSegmentKey = this.keys.buildRBSegmentKey(name); + const rbSegmentFromStorage = this.storage.getItem(rbSegmentKey); + const previous = rbSegmentFromStorage ? JSON.parse(rbSegmentFromStorage) : null; - this.storage.setItem(rbSegmentKey, JSON.stringify(rbSegment)); + this.storage.setItem(rbSegmentKey, JSON.stringify(rbSegment)); - let usesSegmentsDiff = 0; - if (previous && usesSegments(previous)) usesSegmentsDiff--; - if (usesSegments(rbSegment)) usesSegmentsDiff++; - if (usesSegmentsDiff !== 0) this.updateSegmentCount(usesSegmentsDiff); + let usesSegmentsDiff = 0; + if (previous && usesSegments(previous)) usesSegmentsDiff--; + if (usesSegments(rbSegment)) usesSegmentsDiff++; + if (usesSegmentsDiff !== 0) this.updateSegmentCount(usesSegmentsDiff); - return true; - } catch (e) { - this.log.error(LOG_PREFIX + e); - return false; - } + return true; } private remove(name: string): boolean { - try { - const rbSegment = this.get(name); - if (!rbSegment) return false; + const rbSegment = this.get(name); + if (!rbSegment) return false; - this.storage.removeItem(this.keys.buildRBSegmentKey(name)); + this.storage.removeItem(this.keys.buildRBSegmentKey(name)); - if (usesSegments(rbSegment)) this.updateSegmentCount(-1); + if (usesSegments(rbSegment)) this.updateSegmentCount(-1); - return true; - } catch (e) { - this.log.error(LOG_PREFIX + e); - return false; - } + return true; } private getNames(): string[] { diff --git a/src/storages/inLocalStorage/SplitsCacheInLocal.ts b/src/storages/inLocalStorage/SplitsCacheInLocal.ts index 51c8c485..30945684 100644 --- a/src/storages/inLocalStorage/SplitsCacheInLocal.ts +++ b/src/storages/inLocalStorage/SplitsCacheInLocal.ts @@ -80,44 +80,34 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync { } addSplit(split: ISplit) { - try { - const name = split.name; - const splitKey = this.keys.buildSplitKey(name); - const splitFromStorage = this.storage.getItem(splitKey); - const previousSplit = splitFromStorage ? JSON.parse(splitFromStorage) : null; - - if (previousSplit) { - this._decrementCounts(previousSplit); - this.removeFromFlagSets(previousSplit.name, previousSplit.sets); - } + const name = split.name; + const splitKey = this.keys.buildSplitKey(name); + const splitFromStorage = this.storage.getItem(splitKey); + const previousSplit = splitFromStorage ? JSON.parse(splitFromStorage) : null; + + if (previousSplit) { + this._decrementCounts(previousSplit); + this.removeFromFlagSets(previousSplit.name, previousSplit.sets); + } - this.storage.setItem(splitKey, JSON.stringify(split)); + this.storage.setItem(splitKey, JSON.stringify(split)); - this._incrementCounts(split); - this.addToFlagSets(split); + this._incrementCounts(split); + this.addToFlagSets(split); - return true; - } catch (e) { - this.log.error(LOG_PREFIX + e); - return false; - } + return true; } removeSplit(name: string): boolean { - try { - const split = this.getSplit(name); - if (!split) return false; + const split = this.getSplit(name); + if (!split) return false; - this.storage.removeItem(this.keys.buildSplitKey(name)); + this.storage.removeItem(this.keys.buildSplitKey(name)); - this._decrementCounts(split); - this.removeFromFlagSets(split.name, split.sets); + this._decrementCounts(split); + this.removeFromFlagSets(split.name, split.sets); - return true; - } catch (e) { - this.log.error(LOG_PREFIX + e); - return false; - } + return true; } getSplit(name: string): ISplit | null { diff --git a/src/storages/inMemory/RBSegmentsCacheInMemory.ts b/src/storages/inMemory/RBSegmentsCacheInMemory.ts index 2b876202..cd98dc62 100644 --- a/src/storages/inMemory/RBSegmentsCacheInMemory.ts +++ b/src/storages/inMemory/RBSegmentsCacheInMemory.ts @@ -16,9 +16,10 @@ export class RBSegmentsCacheInMemory implements IRBSegmentsCacheSync { } update(toAdd: IRBSegment[], toRemove: IRBSegment[], changeNumber: number): boolean { + let updated = toAdd.map(toAdd => this.add(toAdd)).some(result => result); + updated = toRemove.map(toRemove => this.remove(toRemove.name)).some(result => result) || updated; this.changeNumber = changeNumber; - const updated = toAdd.map(toAdd => this.add(toAdd)).some(result => result); - return toRemove.map(toRemove => this.remove(toRemove.name)).some(result => result) || updated; + return updated; } private add(rbSegment: IRBSegment): boolean { diff --git a/src/sync/polling/updaters/splitChangesUpdater.ts b/src/sync/polling/updaters/splitChangesUpdater.ts index 6c6371e3..b372fa46 100644 --- a/src/sync/polling/updaters/splitChangesUpdater.ts +++ b/src/sync/polling/updaters/splitChangesUpdater.ts @@ -163,8 +163,6 @@ export function splitChangesUpdaterFactory( splitChangesFetcher(since, noCache, till, rbSince, _promiseDecorator) ) .then((splitChanges: ISplitChangesResponse) => { - startingUp = false; - const usedSegments = new Set(); let ffUpdate: MaybeThenable = false; @@ -187,6 +185,8 @@ export function splitChangesUpdaterFactory( ]).then(([ffChanged, rbsChanged]) => { if (storage.save) storage.save(); + startingUp = false; + if (splitsEventEmitter) { // To emit SDK_SPLITS_ARRIVED for server-side SDK, we must check that all registered segments have been fetched return Promise.resolve(!splitsEventEmitter.splitsArrived || ((ffChanged || rbsChanged) && (isClientSide || checkAllSegmentsExist(segments)))) From c09f14d6db7e3259576afd8e893560e2aaac41dd Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Thu, 20 Nov 2025 19:24:40 -0300 Subject: [PATCH 4/5] Add unit test --- CHANGES.txt | 2 +- .../inLocalStorage/MySegmentsCacheInLocal.ts | 7 ++++--- .../__tests__/MySegmentsCacheInLocal.spec.ts | 13 +++++++++++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index e66a3f50..3838af79 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,4 @@ -2.8.1 (November 21, 2025) +2.8.1 (November 25, 2025) - Updated the order of storage operations to prevent inconsistent states when using the `LOCALSTORAGE` storage type and the browser’s `localStorage` fails due to quota limits. 2.8.0 (October 30, 2025) diff --git a/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts b/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts index 1c54fdff..8aec126d 100644 --- a/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +++ b/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts @@ -4,6 +4,8 @@ import { AbstractMySegmentsCacheSync } from '../AbstractMySegmentsCacheSync'; import type { MySegmentsKeyBuilder } from '../KeyBuilderCS'; import { LOG_PREFIX, DEFINED } from './constants'; import { StorageAdapter } from '../types'; +import { MySegmentsData } from '../../sync/polling/types'; +import { IMySegmentsResponse } from '../../dtos/types'; export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync { @@ -16,7 +18,6 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync { this.log = log; this.keys = keys; this.storage = storage; - // There is not need to flush segments cache like splits cache, since resetSegments receives the up-to-date list of active segments } protected addSegment(name: string): boolean { @@ -70,9 +71,9 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync { return n; } - registerSegments() { + resetSegments(segmentsData: MySegmentsData | IMySegmentsResponse) { try { - return super.registerSegments(); + return super.resetSegments(segmentsData); } catch (e) { this.log.error(LOG_PREFIX + e); return false; diff --git a/src/storages/inLocalStorage/__tests__/MySegmentsCacheInLocal.spec.ts b/src/storages/inLocalStorage/__tests__/MySegmentsCacheInLocal.spec.ts index aa218423..f41ae671 100644 --- a/src/storages/inLocalStorage/__tests__/MySegmentsCacheInLocal.spec.ts +++ b/src/storages/inLocalStorage/__tests__/MySegmentsCacheInLocal.spec.ts @@ -39,3 +39,16 @@ test.each(storages)('SEGMENT CACHE / in LocalStorage', (storage) => { expect(storage.getItem(PREFIX + '.user.largeSegment.mocked-segment-2')).toBe('1'); expect(storage.getItem(PREFIX + '.user.largeSegment.mocked-segment')).toBe(null); }); + +test('SEGMENT CACHE / Special case: localStorage failure should not throw an exception', () => { + const cache = new MySegmentsCacheInLocal(loggerMock, new KeyBuilderCS(PREFIX, 'user2'), localStorage); + + // mock localStorage failure + const setItemSpy = jest.spyOn(localStorage, 'setItem').mockImplementation(() => { throw new Error('localStorage failure'); }); + setItemSpy.mockClear(); + + expect(cache.resetSegments({ k: [{ n: 'mocked-segment' }, { n: 'mocked-segment-2' }], cn: 123 })).toBe(false); + expect(setItemSpy).toHaveBeenCalledTimes(1); + + setItemSpy.mockRestore(); +}); From 0176b3bdf6ae2da185ffd2d5dc571fa24bbe062a Mon Sep 17 00:00:00 2001 From: Emiliano Sanchez Date: Tue, 25 Nov 2025 18:23:24 -0300 Subject: [PATCH 5/5] refactor: move startingUp flag update after store in mySegmentsUpdater --- .../inLocalStorage/MySegmentsCacheInLocal.ts | 13 +------------ .../__tests__/MySegmentsCacheInLocal.spec.ts | 13 ------------- src/sync/polling/updaters/mySegmentsUpdater.ts | 6 +++--- 3 files changed, 4 insertions(+), 28 deletions(-) diff --git a/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts b/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts index 8aec126d..0a1cc258 100644 --- a/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts +++ b/src/storages/inLocalStorage/MySegmentsCacheInLocal.ts @@ -2,10 +2,8 @@ import { ILogger } from '../../logger/types'; import { isNaNNumber } from '../../utils/lang'; import { AbstractMySegmentsCacheSync } from '../AbstractMySegmentsCacheSync'; import type { MySegmentsKeyBuilder } from '../KeyBuilderCS'; -import { LOG_PREFIX, DEFINED } from './constants'; +import { DEFINED } from './constants'; import { StorageAdapter } from '../types'; -import { MySegmentsData } from '../../sync/polling/types'; -import { IMySegmentsResponse } from '../../dtos/types'; export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync { @@ -71,13 +69,4 @@ export class MySegmentsCacheInLocal extends AbstractMySegmentsCacheSync { return n; } - resetSegments(segmentsData: MySegmentsData | IMySegmentsResponse) { - try { - return super.resetSegments(segmentsData); - } catch (e) { - this.log.error(LOG_PREFIX + e); - return false; - } - } - } diff --git a/src/storages/inLocalStorage/__tests__/MySegmentsCacheInLocal.spec.ts b/src/storages/inLocalStorage/__tests__/MySegmentsCacheInLocal.spec.ts index f41ae671..aa218423 100644 --- a/src/storages/inLocalStorage/__tests__/MySegmentsCacheInLocal.spec.ts +++ b/src/storages/inLocalStorage/__tests__/MySegmentsCacheInLocal.spec.ts @@ -39,16 +39,3 @@ test.each(storages)('SEGMENT CACHE / in LocalStorage', (storage) => { expect(storage.getItem(PREFIX + '.user.largeSegment.mocked-segment-2')).toBe('1'); expect(storage.getItem(PREFIX + '.user.largeSegment.mocked-segment')).toBe(null); }); - -test('SEGMENT CACHE / Special case: localStorage failure should not throw an exception', () => { - const cache = new MySegmentsCacheInLocal(loggerMock, new KeyBuilderCS(PREFIX, 'user2'), localStorage); - - // mock localStorage failure - const setItemSpy = jest.spyOn(localStorage, 'setItem').mockImplementation(() => { throw new Error('localStorage failure'); }); - setItemSpy.mockClear(); - - expect(cache.resetSegments({ k: [{ n: 'mocked-segment' }, { n: 'mocked-segment-2' }], cn: 123 })).toBe(false); - expect(setItemSpy).toHaveBeenCalledTimes(1); - - setItemSpy.mockRestore(); -}); diff --git a/src/sync/polling/updaters/mySegmentsUpdater.ts b/src/sync/polling/updaters/mySegmentsUpdater.ts index 4b6038c5..5421d3f9 100644 --- a/src/sync/polling/updaters/mySegmentsUpdater.ts +++ b/src/sync/polling/updaters/mySegmentsUpdater.ts @@ -66,10 +66,10 @@ export function mySegmentsUpdaterFactory( new Promise((res) => { updateSegments(segmentsData); res(true); }) : // If not provided, fetch mySegments mySegmentsFetcher(matchingKey, noCache, till, _promiseDecorator).then(segments => { - // Only when we have downloaded segments completely, we should not keep retrying anymore - startingUp = false; - updateSegments(segments); + + // Only when we have downloaded and stored segments completely, we should not keep retrying anymore + startingUp = false; return true; });