import { useEffect , useState , useRef , useMemo } from 'react' ; import { STATUS } from '@proton/shared/lib/models/cache' ; const ERROR_IDX = 2 ; export const getState = ( { value , status } = { status : STATUS . PENDING } , oldState = [ ] ) => { return [ status === STATUS . PENDING || status === STATUS . RESOLVED ? value || oldState [ 0 ] : undefined , status === STATUS . PENDING , status === STATUS . REJECTED ? value : undefined , ] ; } ; const getRecordPending = ( promise ) => { return { status : STATUS . PENDING , value : undefined , promise , timestamp : Date . now ( ) , } ; } ; const getRecordThen = ( promise ) => { return promise . then ( ( value ) => { return { status : STATUS . RESOLVED , value , timestamp : Date . now ( ) , } ; } ) . catch ( ( error ) => { return { status : STATUS . REJECTED , value : error , timestamp : Date . now ( ) , } ; } ) ; } ; param { Object } cache param { Object } key param { Promise } promise return { Object } const update = ( cache , key , promise ) => { const record = getRecordPending ( promise ) ; cache . set ( key , record ) ; getRecordThen ( promise ) . then ( ( newRecord ) => cache . get ( key ) === record && cache . set ( key , newRecord ) ) ; return record ; } ; export const getIsRecordInvalid = ( record , lifetime = Number . MAX_SAFE_INTEGER ) => { return ( ! record || record . status === STATUS . REJECTED || ( record . status === STATUS . RESOLVED && Date . now ( ) - record . timestamp > lifetime ) ) ; } ; export const getPromiseValue = ( cache , key , miss , lifetime ) => { const oldRecord = cache . get ( key ) ; if ( getIsRecordInvalid ( oldRecord , lifetime ) ) { const record = update ( cache , key , miss ( key ) ) ; return record . promise ; } return oldRecord . promise || Promise . resolve ( oldRecord . value ) ; } ; param { Map } cache param { String | undefined } key param { Function } miss return { [ value , loading , error ] } const useCachedModelResult = ( cache , key , miss ) => { const [ forceRefresh , setForceRefresh ] = useState ( ) ; const latestValue = useRef ( ) ; const result = useMemo ( ( ) => { const oldRecord = cache . get ( key ) ; if ( ! oldRecord || ( ! latestValue . current && oldRecord . status === STATUS . REJECTED ) ) { return getState ( update ( cache , key , miss ( key ) ) , latestValue . current ) ; } return getState ( oldRecord , latestValue . current ) ; } , [ cache , key , miss , forceRefresh ] ) ; useEffect ( ( ) => { latestValue . current = result ; } ) ; useEffect ( ( ) => { const checkForChange = ( ) => { const oldRecord = cache . get ( key ) ; if ( ! oldRecord ) { return setForceRefresh ( { } ) ; } const newValue = getState ( oldRecord , latestValue . current ) ; if ( newValue . some ( ( value , i ) => value !== latestValue . current [ i ] ) ) { setForceRefresh ( { } ) ; } } ; const cacheListener = ( changedKey ) => { if ( changedKey !== key ) { return ; } checkForChange ( ) ; } ; checkForChange ( ) ; return cache . subscribe ( cacheListener ) ; } , [ cache , key , miss ] ) ; if ( result [ ERROR_IDX ] ) { throw result [ ERROR_IDX ] ; } return result ; } ; export default useCachedModelResult ;