{-# LANGUAGE OverloadedStrings #-}

module CoinbasePro.WebSocketFeed
    ( subscribeToFeed
    ) where

import           Control.Concurrent                 (forkIO)
import           Control.Exception                  (Exception, throwIO)
import           Control.Monad                      (forever)
import           Data.Aeson                         (decode', encode)
import qualified Network.WebSockets                 as WS
import qualified System.IO.Streams                  as Streams
import           System.IO.Streams.Concurrent.Unagi (makeChanPipe)
import qualified Wuss                               as WU

import           CoinbasePro.Authenticated.Request  (CoinbaseProCredentials (..))
import           CoinbasePro.Environment            (Environment,
                                                     WSConnection (..),
                                                     wsEndpoint)
import           CoinbasePro.Types                  (ProductId)
import           CoinbasePro.WebSocketFeed.Channel  (ChannelMessage (..))
import           CoinbasePro.WebSocketFeed.Request  (ChannelName (..),
                                                     RequestMessageType (..),
                                                     WebSocketFeedRequest (..),
                                                     authenticatedWebSocketFeedRequest)


data ParseException = ParseException deriving Int -> ParseException -> ShowS
[ParseException] -> ShowS
ParseException -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [ParseException] -> ShowS
$cshowList :: [ParseException] -> ShowS
show :: ParseException -> String
$cshow :: ParseException -> String
showsPrec :: Int -> ParseException -> ShowS
$cshowsPrec :: Int -> ParseException -> ShowS
Show
instance Exception ParseException


subscribeToFeed :: [ProductId] -> [ChannelName] -> Environment -> Maybe CoinbaseProCredentials -> IO (Streams.InputStream ChannelMessage)
subscribeToFeed :: [ProductId]
-> [ChannelName]
-> Environment
-> Maybe CoinbaseProCredentials
-> IO (InputStream ChannelMessage)
subscribeToFeed [ProductId]
prds [ChannelName]
channels Environment
env = WSConnection
-> [ProductId]
-> [ChannelName]
-> Maybe CoinbaseProCredentials
-> IO (InputStream ChannelMessage)
subscribe (Environment -> WSConnection
wsEndpoint Environment
env) [ProductId]
prds [ChannelName]
channels


subscribe :: WSConnection -> [ProductId] -> [ChannelName] -> Maybe CoinbaseProCredentials -> IO (Streams.InputStream ChannelMessage)
subscribe :: WSConnection
-> [ProductId]
-> [ChannelName]
-> Maybe CoinbaseProCredentials
-> IO (InputStream ChannelMessage)
subscribe WSConnection
wsConn [ProductId]
prids [ChannelName]
channels Maybe CoinbaseProCredentials
cpc = do
    (InputStream ChannelMessage
is, OutputStream ChannelMessage
os) <- forall a. IO (InputStream a, OutputStream a)
makeChanPipe
    ByteString
req      <- Maybe CoinbaseProCredentials -> IO ByteString
mkWsRequest Maybe CoinbaseProCredentials
cpc

    ThreadId
_ <- IO () -> IO ThreadId
forkIO forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. String -> PortNumber -> String -> ClientApp a -> IO a
WU.runSecureClient String
wsHost PortNumber
wsPort String
"/" forall a b. (a -> b) -> a -> b
$ \Connection
conn -> do
        forall a. WebSocketsData a => Connection -> a -> IO ()
WS.sendTextData Connection
conn ByteString
req
        forall (f :: * -> *) a b. Applicative f => f a -> f b
forever forall a b. (a -> b) -> a -> b
$ Connection -> IO ChannelMessage
parseFeed Connection
conn forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= forall a. OutputStream a -> Maybe a -> IO ()
Streams.writeTo OutputStream ChannelMessage
os forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. a -> Maybe a
Just

    forall (m :: * -> *) a. Monad m => a -> m a
return InputStream ChannelMessage
is
  where
    wsHost :: String
wsHost = WSConnection -> String
host WSConnection
wsConn
    wsPort :: PortNumber
wsPort = WSConnection -> PortNumber
port WSConnection
wsConn

    mkWsRequest :: Maybe CoinbaseProCredentials -> IO ByteString
mkWsRequest   = forall b a. b -> (a -> b) -> Maybe a -> b
maybe (forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ forall a. ToJSON a => a -> ByteString
encode WebSocketFeedRequest
wsRequest) (forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap forall a. ToJSON a => a -> ByteString
encode forall b c a. (b -> c) -> (a -> b) -> a -> c
. CoinbaseProCredentials -> IO AuthenticatedWebSocketFeedRequest
authWsRequest)
    wsRequest :: WebSocketFeedRequest
wsRequest     = RequestMessageType
-> [ProductId] -> [ChannelName] -> WebSocketFeedRequest
WebSocketFeedRequest RequestMessageType
Subscribe [ProductId]
prids [ChannelName]
channels
    authWsRequest :: CoinbaseProCredentials -> IO AuthenticatedWebSocketFeedRequest
authWsRequest = WebSocketFeedRequest
-> CoinbaseProCredentials -> IO AuthenticatedWebSocketFeedRequest
authenticatedWebSocketFeedRequest WebSocketFeedRequest
wsRequest


parseFeed :: WS.Connection -> IO ChannelMessage
parseFeed :: Connection -> IO ChannelMessage
parseFeed Connection
conn = forall a. WebSocketsData a => Connection -> IO a
WS.receiveData Connection
conn forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= forall b a. b -> (a -> b) -> Maybe a -> b
maybe forall {a}. IO a
err forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a. FromJSON a => ByteString -> Maybe a
decode'
  where err :: IO a
err = forall e a. Exception e => e -> IO a
throwIO ParseException
ParseException