import { AxiosRequestConfig , AxiosResponse } from 'axios' ; import { stringify } from 'query-string' ; import { CacheTarget , CacheTargets , cacheFactory , SUPPORTED_TARGETS } from './Cache' ; export const CACHE_CONFIG_30MIN = { expiresIn : 1800 } ; export const CACHE_CONFIG_NOCACHE = { expiresIn : 0 } ; export const CACHE_CONFIG_30MIN_MEMORY = { targets : [ CacheTarget . MEMORY ] , expiresIn : CACHE_CONFIG_30MIN . expiresIn , } ; export const EXPIRY_HEADER_KEY = 'cache_expires' ; export type CacheConfig = { expiresIn : number ; targets ? : CacheTargets ; } ; export const cacheableRequestsInProgress = new Set ( ) ; export const removeCacheableRequestsInProgress = ( cacheKey ) : void => { cacheableRequestsInProgress . delete ( cacheKey ) ; } ; export const fetchCachedResponse = async ( request ) : Promise < any > => { if ( ! isRequestCachable ( request ) ) { return request ; } const cacheKey = generateCacheKey ( request ) ; if ( cacheKey === null ) { return request ; } if ( request . cancelToken && request . setCancelTokenCacheKey ) { request . setCancelTokenCacheKey ( cacheKey ) ; } while ( cacheableRequestsInProgress . has ( cacheKey ) ) { await sleep ( 100 ) ; } cacheableRequestsInProgress . add ( cacheKey ) ; try { const shCache = await cacheFactory ( request . cache . targets ) ; if ( ! shCache ) { return request ; } const cachedResponse = await shCache . get ( cacheKey , request . responseType ) ; if ( ! cachedResponse || ! cacheStillValid ( cachedResponse . headers ) ) { request . cacheKey = cacheKey ; return request ; } request . adapter = async ( ) => { return Promise . resolve ( { data : cachedResponse . data , headers : request . headers , request : request , config : request , responseType : request . responseType , } ) ; } ; return request ; } finally { if ( ! request . cacheKey ) { removeCacheableRequestsInProgress ( cacheKey ) ; } } } ; export const saveCacheResponse = async ( response ) : Promise < any > => { if ( ! isRequestCachable ( response . config ) ) { return response ; } if ( ! response . config . cacheKey ) { return response ; } try { const shCache = await cacheFactory ( response . config . cache . targets ) ; if ( ! shCache ) { return response ; } if ( ( await shCache . has ( response . config . cacheKey ) ) && cacheStillValid ( await shCache . getHeaders ( response . config . cacheKey ) ) ) { return response ; } const expiresMs = new Date ( ) . getTime ( ) + response . config . cache . expiresIn * 1000 ; response . headers = { ... response . headers , [ EXPIRY_HEADER_KEY ] : expiresMs , } ; shCache . set ( response . config . cacheKey , response ) ; return response ; } finally { removeCacheableRequestsInProgress ( response . config . cacheKey ) ; } } ; export const cacheStillValid = ( headers ) : boolean => { if ( ! headers ) { return true ; } const now = new Date ( ) ; const expirationDate = Number ( headers [ EXPIRY_HEADER_KEY ] ) ; return expirationDate > now . getTime ( ) ; } ; const generateCacheKey = ( request ) : string | null => { switch ( request . method ) { case 'post' : const body = typeof request . data === 'string' ? request . data : JSON . stringify ( request . data ) ; const hash = stringToHash ( body ) ; return `${ request . url }?${ hash }` ; case 'get' : return `${ request . url }?${ stringify ( request . params ) }` ; default : return null ; } } ; const stringToHash = ( message ) : number => { let hash = 0 ; for ( let i = 0 ; i < message . length ; i ++ ) { let chr = message . charCodeAt ( i ) ; hash = ( hash << 5 ) - hash + chr ; hash |= 0 ; } return hash ; } ; export const findAndDeleteExpiredCachedItems = async ( ) : Promise < void > => { for ( const target of SUPPORTED_TARGETS ) { const shCache = await cacheFactory ( [ target ] ) ; if ( ! shCache ) { continue ; } const cacheKeys = await shCache . keys ( ) ; cacheKeys . forEach ( async ( key ) => { const headers = await shCache . getHeaders ( key ) ; if ( ! cacheStillValid ( headers ) ) { await shCache . delete ( key ) ; } } ) ; } } ; const isRequestCachable = ( request ) : boolean => { if ( ! ( request && request . cache && request . cache . expiresIn ) ) { return false ; } if ( request . cache . expiresIn === 0 ) { return false ; } return true ; } ; const sleep = async ( sleepTimeMs ) : Promise < void > => { await new Promise ( resolve => { setTimeout ( resolve , sleepTimeMs ) ; } ) ; } ;