/**
* @namespace Services
* @category Services
* @module cache-service
* */
import localforageWrapper from '@/utils/cache'
import cacheTimes from '@/config/cache-times.json'
import { getQueryStringValue } from '@/utils/querystring'
const shouldLog =
getQueryStringValue('cache_verbose') === '1' ||
getQueryStringValue('cache_debug') === '1' ||
getQueryStringValue('verbose') === '1'
export function createLocalStorage(namespacePrefix = '') {
namespacePrefix = namespacePrefix ? namespacePrefix + '_' : ''
let storage = {
name: namespacePrefix,
localforage: localforageWrapper,
keys: async () =>
(await localforageWrapper.keys()).filter((key) =>
key.includes(namespacePrefix)
),
setItem(key, value) {
return localforageWrapper.setItem(`${namespacePrefix}${key}`, value)
},
async getItem(key, defaultValue = null) {
let value = await localforageWrapper.getItem(`${namespacePrefix}${key}`)
return value === null ? defaultValue : value
},
async clear() {
let keys = await this.localforage.keys()
await Promise.all(
keys
.filter((k) => k.indexOf(namespacePrefix) === 0)
.map((k) => this.localforage.removeItem(k))
)
},
/**
* Simple cache invalidation by string match
* i.g: this.$localStorage.invalidateCacheByKeyInclude('cdm/messages_alertes') will remove cache for Alerts module main search
*/
async invalidateCacheByKeyInclude(str) {
let keys = await this.keys()
let promises = keys
.filter((k) => k.includes(str))
.map((key) => {
console.log('invalidateCacheByKeyInclude::invalidate', key)
return this.localforage.removeItem(key)
})
await Promise.all(promises)
if (promises.length === 0) {
shouldLog &&
console.log('invalidateCacheByKeyInclude::no-match', {
keys,
})
}
return promises.length > 0
},
}
storage.cache = createCache(storage, {})
return storage
}
/**
* Cache wrapper on top of localforage (Used for API calls caching)
*
* @param {*} storage
* @param {*} options
*/
function createCache(storage, options = {}) {
/**
* Retrieves and env value if available
* @TODO: Unit test
*
* @param {String} configValue
* @param {Mixed} defaultValue
*
*/
function getCacheTimeValueFromConfig(configValue, defaultValue = null) {
defaultValue = !isNaN(defaultValue) ? parseInt(defaultValue) : null
if (!defaultValue && configValue.indexOf('||')) {
defaultValue = !isNaN(configValue.split('||')[1])
? parseInt(configValue.split('||')[1]) || defaultValue
: defaultValue
configValue = configValue.split('||')[0]
shouldLog &&
console.log('getCacheTimeValueFromConfig', {
defaultValue,
configValue,
})
}
let timeValue =
(process.env[configValue] &&
![undefined, ''].includes(process.env[configValue]) &&
!isNaN(parseInt(process.env[configValue])) &&
parseInt(process.env[configValue])) ||
defaultValue
return (!isNaN(timeValue) && timeValue) || defaultValue
}
return {
/**
* Checks if cache should be disabled via an special URL parameter
*/
shouldDisableCache: () =>
getQueryStringValue('nocache') === '1' ||
getQueryStringValue('cache') === '0',
/**
* Retrieves cache times per key (normalized)
*
*/
getCacheTimes() {
let table = cacheTimes || {}
return Object.keys(table).reduce((acum, key) => {
let cacheTime = getCacheTimeValueFromConfig(
table[key],
getCacheTimeValueFromConfig(
'process.env.VUE_APP_CACHE_TIME_DEFAULT',
null
)
)
if (cacheTime !== null) {
acum[key.split('/').join('_')] = cacheTime
}
return acum
}, {})
},
/**
* Retrieve data from cache if key has an expiration time and cache is enabled and cached data is not expired
* @param {*} url
*/
async get(key) {
let storageKey = `cache__${key}`
key = key.split('/').join('_')
let keyTimes = key.split('__uniq__')[0]
if (this.shouldDisableCache()) {
shouldLog && console.log(`${storage.name}:cache:get`, key, 'disabled')
return null
}
const shouldUseCache = this.getCacheTime(keyTimes)
if (!shouldUseCache) {
shouldLog && console.log(`${storage.name}:cache:get`, key, 'skip')
return null
}
let item = await storage.getItem(storageKey, null)
const isExpired = item
? Date.now() - item.date > parseInt(this.getCacheTime(keyTimes))
: true
if (item) {
if (isExpired) {
shouldLog &&
console.log(`${storage.name}:cache:get`, key, 'expired', {
storageKey,
item,
cacheAge: Date.now() - item.date,
cacheRetention: parseInt(this.getCacheTime(keyTimes)),
})
storage.localforage.removeItem(storageKey)
} else {
shouldLog && console.log(`${storage.name}:cache:get`, key, 'success')
return item.value
}
} else {
shouldLog &&
console.log(`${storage.name}:cache:get`, key, 'not-found', {
storageKey,
})
}
return null
},
/**
* Get cache time value from object
* Supports retrieving value by matching a key who contains a wildcard
* @example
*
* {
* "/v2/temps_reel/by_vehicule**":"1000"
* }
* getCacheTime(times, "/v2/temps_reel/by_vehicule?vehicule_id=3125")
*
* //Will return 1000
*
* @param {*} times
* @param {*} key
* @returns
*/
getCacheTime(key) {
this._times = this._times || this.getCacheTimes()
let cacheKeys = Object.keys(this._times).map((_key) =>
_key.split('/').join('_')
)
let keyToCompare = key.split('/').join('_')
let originAsKey = window.location.origin.split('/').join('_')
if (key.includes(originAsKey)) {
keyToCompare = key.split(originAsKey).join('').split('/').join('_')
}
let firstMatchedCacheKey = cacheKeys.find((cacheKey) => {
cacheKey = cacheKey.split('**')[0] //If wildcard, the the left part
return keyToCompare.includes(cacheKey)
})
let cacheTime = firstMatchedCacheKey
? this._times[firstMatchedCacheKey]
: null
const isCacheTimeFound = !!firstMatchedCacheKey
const isWildcardCacheKey = firstMatchedCacheKey
? firstMatchedCacheKey.includes('**')
: false
shouldLog &&
console.log(
`cache:time:${
isCacheTimeFound
? 'found' + `${isWildcardCacheKey ? '(wildcard)' : ''}`
: 'not-found'
}`,
{
keyToCompare,
cacheKeys,
}
)
return cacheTime
},
/**
* Check if a key has an expiration time and if cache is enabled
* @param {*} key
*/
shouldCache(key) {
key = key.split('/').join('_')
key = key.split('__uniq__')[0]
shouldLog &&
console.log(`${storage.name}:cache:shouldCache`, key, {
shouldCache: this.getCacheTime(key) && !this.shouldDisableCache(),
isCacheDisabled: this.shouldDisableCache(),
})
return !!this.getCacheTime(key) && !this.shouldDisableCache()
},
/**
* Store data if the key has an expiration time and cache is enabled
*
* @param {*} key
* @param {*} value
*/
set(key, value) {
if (this.shouldDisableCache()) {
shouldLog &&
console.log(`${storage.name}:cache:set (skip, disabled)`, key)
return
}
let storageKey = `cache__${key}`
key = key.split('/').join('_')
let keyTimes = key.split('__uniq__')[0]
let timeCount = this.getCacheTime(keyTimes)
if (timeCount) {
let payload = {
date: Date.now(),
value,
}
shouldLog && console.log(`${storage.name}:cache:set`, key)
return storage.setItem(storageKey, payload)
} else {
shouldLog &&
console.log(`${storage.name}:cache:set (skip)`, key, {
keyTimes,
timeCount,
})
}
},
}
}
Source