cdp-0.0.1.0: src/CDP/Internal/Utils.hs
{-# LANGUAGE OverloadedStrings, RecordWildCards, TupleSections, GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
module CDP.Internal.Utils where
import Control.Monad
import Control.Monad.Loops
import Control.Monad.Trans (liftIO)
import qualified Data.Map as M
import Data.Maybe
import Data.Foldable (for_)
import Data.Functor.Identity
import Data.String
import qualified Data.Text as T
import qualified Data.List as List
import qualified Data.Text.IO as TI
import qualified Data.Vector as V
import Data.Aeson.Types (Parser(..))
import Data.Aeson (FromJSON (..), ToJSON (..), (.:), (.:?), (.=), (.!=), (.:!))
import qualified Data.Aeson as A
import qualified Network.WebSockets as WS
import Control.Concurrent
import qualified Data.ByteString.Lazy as BS
import qualified Data.Map as Map
import Data.Proxy
import System.Random
import Control.Applicative
import Data.Default
import Control.Exception
import System.Timeout
import Data.Char
import qualified System.IO as IO
import qualified Data.IORef as IORef
newtype CommandId = CommandId { unCommandId :: Int }
deriving (Eq, Ord, Show, FromJSON, ToJSON)
type CommandResponseBuffer =
Map.Map CommandId (MVar (Either ProtocolError A.Value))
type SessionId = T.Text
data Subscriptions = Subscriptions
{ subscriptionsHandlers :: Map.Map (String, Maybe SessionId) (Map.Map Int (A.Value -> IO ()))
, subscriptionsNextId :: Int
}
data Handle = Handle
{ config :: Config
, commandNextId :: MVar CommandId
, subscriptions :: IORef.IORef Subscriptions
, commandBuffer :: IORef.IORef CommandResponseBuffer
, conn :: WS.Connection
, listenThread :: ThreadId
, responseBuffer :: MVar [(String, BS.ByteString)]
}
data Config = Config
{ hostPort :: (String, Int)
-- | WebSocket path to connect to.
-- If Nothing, the initial connection is made to the browser.
, path :: Maybe String
, doLogResponses :: Bool
-- | Number of microseconds to wait for a command response.
-- Waits forever if Nothing.
, commandTimeout :: Maybe Int
} deriving Show
instance Default Config where
def = Config{..}
where
hostPort = ("http://127.0.0.1", 9222)
path = def
doLogResponses = False
commandTimeout = def
class FromJSON a => Event a where
eventName :: Proxy a -> String
class (ToJSON cmd, FromJSON (CommandResponse cmd)) => Command cmd where
type CommandResponse cmd :: *
commandName :: Proxy cmd -> String
fromJSON :: Proxy cmd -> A.Value -> A.Result (CommandResponse cmd)
fromJSON = const A.fromJSON
data ProtocolError =
PEParse String -- ^ Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text
| PEInvalidRequest String -- ^ The JSON sent is not a valid Request object
| PEMethodNotFound String -- ^ The method does not exist / is not available
| PEInvalidParams String -- ^ Invalid method parameter (s)
| PEInternalError String -- ^ Internal JSON-RPC error
| PEServerError String -- ^ Server error
| PEOther String -- ^ An uncategorized error
deriving Eq
instance Exception ProtocolError
instance Show ProtocolError where
show (PEParse msg) = unlines ["Server parsing protocol error:", msg]
show (PEInvalidRequest msg) = unlines ["Invalid request protocol error:", msg]
show (PEMethodNotFound msg) = unlines ["Method not found protocol error:", msg]
show (PEInvalidParams msg) = unlines ["Invalid params protocol error:", msg]
show (PEInternalError msg) = unlines ["Internal protocol error:", msg]
show (PEServerError msg) = unlines ["Server protocol error:", msg]
show (PEOther msg) = unlines ["Other protocol error:", msg]
instance FromJSON ProtocolError where
parseJSON = A.withObject "ProtocolError" $ \obj -> do
code <- obj .: "code"
msg <- obj .: "message"
pure $ case (code :: Double) of
-32700 -> PEParse msg
-32600 -> PEInvalidRequest msg
-32601 -> PEMethodNotFound msg
-32602 -> PEInvalidParams msg
-32603 -> PEInternalError msg
_ -> if code > -32099 && code < -32000 then PEServerError msg else PEOther msg
data Error =
ERRNoResponse
| ERRParse String
| ERRProtocol ProtocolError
deriving Eq
instance Exception Error
instance Show Error where
show ERRNoResponse = "no response received from the browser"
show (ERRParse msg) = unlines ["error in parsing a message received from the browser:", msg]
show (ERRProtocol pe) = unlines ["error encountered by the browser:", show pe]
uncapitalizeFirst :: String -> String
uncapitalizeFirst [] = []
uncapitalizeFirst (x:xs) = toLower x : xs