module Control.Concurrent.Async.Refresh.Tokens
( IsToken(..)
, Token(..)
, TokenRefresher
, tokenRefresherAsync
, RequestToken(..)
, RefreshResult(..)
, TokenStore
, TokenConf
, newTokenRefresher
, newEmptyTokenStore
, defaultTokenConf
, tokenConfSetFactor
, tokenConfAddRequest
) where
import Control.Concurrent.Async.Refresh.Tokens.Prelude
import Control.Concurrent.Async.Lifted.Safe (cancel,
waitAny)
import Control.Concurrent.Async.Refresh
import Control.Concurrent.Async.Refresh.Tokens.Conf
import qualified Control.Concurrent.Async.Refresh.Tokens.Lenses as Lens
import Control.Concurrent.Async.Refresh.Tokens.Types
import Lens.Micro
newTokenRefresher :: forall m.
( MonadIO m
, MonadBaseControl IO m
, MonadMask m
, MonadLogger m
, Forall (Pure m) )
=> TokenConf m -> m TokenRefresher
newTokenRefresher conf = do
asyncHandle <- async $
bracket (mapM spawnSingleTokenRefresher (conf ^. Lens.requests))
(mapM cancel)
waitForTermination
return $ TokenRefresher asyncHandle
where waitForTermination :: [Async ()] -> m ()
waitForTermination asyncHandles = do
void $ waitAny asyncHandles
logErrorN "Token Refresher terminated unexpectedly"
spawnSingleTokenRefresher :: forall m.
( MonadIO m
, MonadBaseControl IO m
, MonadMask m
, MonadLogger m
, Forall (Pure m) )
=> RequestToken m -> m (Async ())
spawnSingleTokenRefresher (RequestToken store action) = do
let conf = newAsyncRefreshConf action
& asyncRefreshConfSetCallback (tokenStoreCallback store)
asyncRefreshAsync <$> newAsyncRefresh conf
tokenRefresherAsync :: TokenRefresher -> Async ()
tokenRefresherAsync (TokenRefresher asyncHandle) = asyncHandle
tokenStoreCallback :: forall m t.
(MonadIO m, IsToken t, MonadLogger m)
=> TokenStore t
-> Either SomeException (RefreshResult (Token t)) -> m ()
tokenStoreCallback _ (Left exn) =
logErrorN $ sformat ("Token refresh action failed: " % stext) (tshow exn)
tokenStoreCallback store res@(Right t) = do
logDebugN $
sformat ("Token refresh action succeeded: " % stext) (tshow (maskRefreshResult t))
atomically $ writeTVar store (refreshResult <$> res)
maskRefreshResult :: RefreshResult (Token t) -> RefreshResult (Token t)
maskRefreshResult res = res { refreshResult = maskedToken }
maskedToken :: Token t
maskedToken = Token { unToken = "XXXXXXXXXXXXXXXX" }
newEmptyTokenStore :: (MonadIO m, IsToken t)
=> m (TVar (Either SomeException (Token t)))
newEmptyTokenStore = atomically $
newTVar (Left (toException (TokenNotFound "")))