import { ISettings } from '../../types'; import { isFiniteNumber, isNaNNumber } from '../../utils/lang'; import { getStorageHash } from '../KeyBuilder'; import { LOG_PREFIX } from './constants'; import type { SplitsCacheInLocal } from './SplitsCacheInLocal'; import type { RBSegmentsCacheInLocal } from './RBSegmentsCacheInLocal'; import type { MySegmentsCacheInLocal } from './MySegmentsCacheInLocal'; import { KeyBuilderCS } from '../KeyBuilderCS'; import SplitIO from '../../../types/splitio'; import { StorageAdapter } from '../types'; const DEFAULT_CACHE_EXPIRATION_IN_DAYS = 10; const MILLIS_IN_A_DAY = 86400000; /** * Validates if cache should be cleared and sets the cache `hash` if needed. * * @returns `true` if cache should be cleared, `false` otherwise */ function validateExpiration(options: SplitIO.InLocalStorageOptions, storage: StorageAdapter, settings: ISettings, keys: KeyBuilderCS, currentTimestamp: number, isThereCache: boolean) { const { log, initialRolloutPlan } = settings; // Check expiration const lastUpdatedTimestamp = parseInt(storage.getItem(keys.buildLastUpdatedKey()) as string, 10); if (!isNaNNumber(lastUpdatedTimestamp)) { const cacheExpirationInDays = isFiniteNumber(options.expirationDays) && options.expirationDays >= 1 ? options.expirationDays : DEFAULT_CACHE_EXPIRATION_IN_DAYS; const expirationTimestamp = currentTimestamp - MILLIS_IN_A_DAY * cacheExpirationInDays; if (lastUpdatedTimestamp < expirationTimestamp) { log.info(LOG_PREFIX + 'Cache expired more than ' + cacheExpirationInDays + ' days ago. Cleaning up cache'); return true; } } // Check hash const storageHashKey = keys.buildHashKey(); const storageHash = storage.getItem(storageHashKey); const currentStorageHash = getStorageHash(settings); if (storageHash !== currentStorageHash) { try { storage.setItem(storageHashKey, currentStorageHash); } catch (e) { log.error(LOG_PREFIX + e); } if (isThereCache && !initialRolloutPlan) { log.info(LOG_PREFIX + 'SDK key, flags filter criteria, or flags spec version has changed. Cleaning up cache'); return true; } return false; // No cache to clear } // Clear on init if (options.clearOnInit) { const lastClearTimestamp = parseInt(storage.getItem(keys.buildLastClear()) as string, 10); if (isNaNNumber(lastClearTimestamp) || lastClearTimestamp < currentTimestamp - MILLIS_IN_A_DAY) { log.info(LOG_PREFIX + 'clearOnInit was set and cache was not cleared in the last 24 hours. Cleaning up cache'); return true; } } } /** * Clean cache if: * - it has expired, i.e., its `lastUpdated` timestamp is older than the given `expirationTimestamp` * - its hash has changed, i.e., the SDK key, flags filter criteria or flags spec version was modified * - `clearOnInit` was set and cache was not cleared in the last 24 hours * * @returns Metadata object with `initialCacheLoad` (true if is fresh install, false if is ready from cache) and `lastUpdateTimestamp` (timestamp of last cache update or undefined) */ export function validateCache(options: SplitIO.InLocalStorageOptions, storage: StorageAdapter, settings: ISettings, keys: KeyBuilderCS, splits: SplitsCacheInLocal, rbSegments: RBSegmentsCacheInLocal, segments: MySegmentsCacheInLocal, largeSegments: MySegmentsCacheInLocal): Promise { return Promise.resolve(storage.load && storage.load()).then(() => { const currentTimestamp = Date.now(); const isThereCache = splits.getChangeNumber() > -1; // Get lastUpdateTimestamp from storage const lastUpdatedTimestampStr = storage.getItem(keys.buildLastUpdatedKey()); const lastUpdatedTimestamp = lastUpdatedTimestampStr ? parseInt(lastUpdatedTimestampStr, 10) : undefined; const lastUpdateTimestamp = (!isNaNNumber(lastUpdatedTimestamp) && lastUpdatedTimestamp !== undefined) ? lastUpdatedTimestamp : undefined; if (validateExpiration(options, storage, settings, keys, currentTimestamp, isThereCache)) { splits.clear(); rbSegments.clear(); segments.clear(); largeSegments.clear(); // Update last clear timestamp try { storage.setItem(keys.buildLastClear(), currentTimestamp + ''); } catch (e) { settings.log.error(LOG_PREFIX + e); } // Persist clear if (storage.save) storage.save(); return { initialCacheLoad: true, // Cache was cleared, so this is an initial load (no cache existed) lastUpdateTimestamp: undefined }; } // Check if ready from cache return { initialCacheLoad: !isThereCache, // true if no cache exists (initial load), false if cache exists (ready from cache) lastUpdateTimestamp }; }); }