diff --git a/src/storages/dataLoader.ts b/src/storages/dataLoader.ts index ce288868..55535cfd 100644 --- a/src/storages/dataLoader.ts +++ b/src/storages/dataLoader.ts @@ -1,7 +1,9 @@ import { PreloadedData } from '../types'; -import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../utils/constants/browser'; import { DataLoader, ISegmentsCacheSync, ISplitsCacheSync } from './types'; +// This value might be eventually set via a config parameter +const DEFAULT_CACHE_EXPIRATION_IN_MILLIS = 864000000; // 10 days + /** * Factory of client-side storage loader * diff --git a/src/storages/inLocalStorage/index.ts b/src/storages/inLocalStorage/index.ts index f32cc014..bb01bd7d 100644 --- a/src/storages/inLocalStorage/index.ts +++ b/src/storages/inLocalStorage/index.ts @@ -50,7 +50,7 @@ export function InLocalStorage(options: SplitIO.InLocalStorageOptions = {}): ISt uniqueKeys: impressionsMode === NONE ? new UniqueKeysCacheInMemoryCS() : undefined, validateCache() { - return validateCache(settings, keys, splits, segments, largeSegments); + return validateCache(options, settings, keys, splits, segments, largeSegments); }, destroy() { }, diff --git a/src/storages/inLocalStorage/validateCache.ts b/src/storages/inLocalStorage/validateCache.ts index f76b77ca..d2da969a 100644 --- a/src/storages/inLocalStorage/validateCache.ts +++ b/src/storages/inLocalStorage/validateCache.ts @@ -1,21 +1,28 @@ import { ISettings } from '../../types'; -import { DEFAULT_CACHE_EXPIRATION_IN_MILLIS } from '../../utils/constants/browser'; -import { isNaNNumber } from '../../utils/lang'; +import { isFiniteNumber, isNaNNumber } from '../../utils/lang'; import { getStorageHash } from '../KeyBuilder'; import { LOG_PREFIX } from './constants'; import type { SplitsCacheInLocal } from './SplitsCacheInLocal'; import type { MySegmentsCacheInLocal } from './MySegmentsCacheInLocal'; import { KeyBuilderCS } from '../KeyBuilderCS'; +import SplitIO from '../../../types/splitio'; -function validateExpiration(settings: ISettings, keys: KeyBuilderCS) { +// milliseconds in a day +const DEFAULT_CACHE_EXPIRATION_IN_DAYS = 10; +const MILLIS_IN_A_DAY = 86400000; + +function validateExpiration(options: SplitIO.InLocalStorageOptions, settings: ISettings, keys: KeyBuilderCS) { const { log } = settings; // Check expiration - const expirationTimestamp = Date.now() - DEFAULT_CACHE_EXPIRATION_IN_MILLIS; + const expirationTimestamp = Date.now() - MILLIS_IN_A_DAY * (isFiniteNumber(options.expirationDays) && options.expirationDays >= 1 ? options.expirationDays : DEFAULT_CACHE_EXPIRATION_IN_DAYS); let value: string | number | null = localStorage.getItem(keys.buildLastUpdatedKey()); if (value !== null) { value = parseInt(value, 10); - if (!isNaNNumber(value) && value < expirationTimestamp) return true; + if (!isNaNNumber(value) && value < expirationTimestamp) { + log.info(LOG_PREFIX + 'Cache expired. Cleaning up cache'); + return true; + } } // Check hash @@ -24,7 +31,7 @@ function validateExpiration(settings: ISettings, keys: KeyBuilderCS) { const currentStorageHash = getStorageHash(settings); if (storageHash !== currentStorageHash) { - log.info(LOG_PREFIX + 'SDK key, flags filter criteria or flags spec version was modified. Updating cache'); + log.info(LOG_PREFIX + 'SDK key, flags filter criteria or flags spec version was modified. Cleaning up cache'); try { localStorage.setItem(storageHashKey, currentStorageHash); } catch (e) { @@ -39,9 +46,9 @@ function validateExpiration(settings: ISettings, keys: KeyBuilderCS) { * - it has expired, i.e., its `lastUpdated` timestamp is older than the given `expirationTimestamp` * - hash has changed, i.e., the SDK key, flags filter criteria or flags spec version was modified */ -export function validateCache(settings: ISettings, keys: KeyBuilderCS, splits: SplitsCacheInLocal, segments: MySegmentsCacheInLocal, largeSegments: MySegmentsCacheInLocal): boolean { +export function validateCache(options: SplitIO.InLocalStorageOptions, settings: ISettings, keys: KeyBuilderCS, splits: SplitsCacheInLocal, segments: MySegmentsCacheInLocal, largeSegments: MySegmentsCacheInLocal): boolean { - if (validateExpiration(settings, keys)) { + if (validateExpiration(options, settings, keys)) { splits.clear(); segments.clear(); largeSegments.clear(); diff --git a/src/utils/constants/browser.ts b/src/utils/constants/browser.ts deleted file mode 100644 index d627f780..00000000 --- a/src/utils/constants/browser.ts +++ /dev/null @@ -1,2 +0,0 @@ -// This value might be eventually set via a config parameter -export const DEFAULT_CACHE_EXPIRATION_IN_MILLIS = 864000000; // 10 days diff --git a/src/utils/lang/index.ts b/src/utils/lang/index.ts index 11b6afd0..b1a7e35a 100644 --- a/src/utils/lang/index.ts +++ b/src/utils/lang/index.ts @@ -120,7 +120,7 @@ export function isBoolean(val: any): boolean { * Unlike `Number.isFinite`, it also tests Number object instances. * Unlike global `isFinite`, it returns false if the value is not a number or Number object instance. */ -export function isFiniteNumber(val: any): boolean { +export function isFiniteNumber(val: any): val is number { if (val instanceof Number) val = val.valueOf(); return typeof val === 'number' ? Number.isFinite ? Number.isFinite(val) : isFinite(val) : diff --git a/types/splitio.d.ts b/types/splitio.d.ts index bb108c1c..4aa59db3 100644 --- a/types/splitio.d.ts +++ b/types/splitio.d.ts @@ -906,6 +906,18 @@ declare namespace SplitIO { * @defaultValue `'SPLITIO'` */ prefix?: string; + /** + * Number of days before cached data expires if it was not updated. If cache expires, it is cleared on initialization. + * + * @defaultValue `10` + */ + expirationDays?: number; + /** + * Optional settings to clear the cache. If set to `true`, the SDK clears the cached data on initialization, unless the cache was cleared within the last 24 hours. + * + * @defaultValue `false` + */ + clearOnInit?: boolean; } /** * Storage for asynchronous (consumer) SDK. @@ -1229,11 +1241,23 @@ declare namespace SplitIO { */ type?: BrowserStorage; /** - * Optional prefix to prevent any kind of data collision between SDK versions. + * Optional prefix to prevent any kind of data collision between SDK versions when using 'LOCALSTORAGE'. * * @defaultValue `'SPLITIO'` */ prefix?: string; + /** + * Optional settings for the 'LOCALSTORAGE' storage type. It specifies the number of days before cached data expires if it was not updated. If cache expires, it is cleared on initialization. + * + * @defaultValue `10` + */ + expirationDays?: number; + /** + * Optional settings for the 'LOCALSTORAGE' storage type. If set to `true`, the SDK clears the cached data on initialization, unless the cache was cleared within the last 24 hours. + * + * @defaultValue `false` + */ + clearOnInit?: boolean; }; } /**