{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Network.Web3.Provider where
import Control.Concurrent.Async (Async, async)
import Control.Exception (Exception, try)
import Control.Monad.Catch (MonadThrow)
import Control.Monad.IO.Class (MonadIO (..))
import Control.Monad.State (MonadState (..))
import Control.Monad.Trans.State (StateT, evalStateT, withStateT)
import Data.Default (Default (..))
import Data.Text (Text)
import GHC.Generics (Generic)
import Network.HTTP.Client (Manager)
import qualified Network.Socket as S
import qualified Network.WebSockets as WS (Connection,
defaultConnectionOptions,
newClientConnection,
sendClose)
import qualified Network.WebSockets.Stream as Stream
import Network.JsonRpc.TinyClient (JsonRpc, JsonRpcClient (..),
defaultSettings, jsonRpcManager)
newtype Web3 a = Web3 { unWeb3 :: StateT JsonRpcClient IO a }
deriving (Functor, Applicative, Monad, MonadIO, MonadThrow, MonadState JsonRpcClient)
instance JsonRpc Web3
data Web3Error = JsonRpcFail !String
| ParserFail !String
| UserFail !String
deriving (Show, Eq, Generic)
instance Exception Web3Error
data Provider = HttpProvider String
| WsProvider String Int
deriving (Show, Eq, Generic)
instance Default Provider where
def = HttpProvider "http://localhost:8545"
runWeb3With :: MonadIO m
=> Manager
-> Provider
-> Web3 a
-> m (Either Web3Error a)
runWeb3With manager provider f = do
runWeb3' provider Web3 { unWeb3 = withStateT changeManager $ unWeb3 f}
where
changeManager jRpcClient = case jRpcClient of
JsonRpcHttpClient{..} -> jRpcClient { jsonRpcManager = manager }
JsonRpcWsClient{..} -> jRpcClient
runWeb3' :: MonadIO m
=> Provider
-> Web3 a
-> m (Either Web3Error a)
runWeb3' (HttpProvider uri) f = do
cfg <- defaultSettings uri
liftIO . try . flip evalStateT cfg . unWeb3 $ f
runWeb3' (WsProvider host port) f = do
connection <- liftIO $ getConnection host port "/"
let currentClient = JsonRpcWsClient { jsonRpcWsConnection = connection }
response <- liftIO $ try . flip evalStateT currentClient . unWeb3 $ f
liftIO $ WS.sendClose connection ("Bye-" :: Text)
return response
runWeb3 :: MonadIO m
=> Web3 a
-> m (Either Web3Error a)
{-# INLINE runWeb3 #-}
runWeb3 = runWeb3' def
forkWeb3 :: Web3 a -> Web3 (Async a)
forkWeb3 f = liftIO . async . evalStateT (unWeb3 f) =<< get
getConnection :: String
-> Int
-> String
-> IO WS.Connection
{-# INLINE getConnection #-}
getConnection host port path = do
let hints = S.defaultHints
{S.addrSocketType = S.Stream}
fullHost = if port == 80 then host else (host ++ ":" ++ show port)
path0 = if null path then "/" else path
addr:_ <- S.getAddrInfo (Just hints) (Just host) (Just $ show port)
sock <- S.socket (S.addrFamily addr) S.Stream S.defaultProtocol
S.setSocketOption sock S.NoDelay 1
res <- ( S.connect sock (S.addrAddress addr) >>
Stream.makeSocketStream sock) >>=
(\stream ->
WS.newClientConnection stream fullHost
path0 WS.defaultConnectionOptions [] )
return res