cdp-0.0.1.0: src/CDP/Endpoints.hs
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
module CDP.Endpoints where
import Data.Maybe
import Data.List
import Data.Proxy
import qualified Network.URI as Uri
import qualified Network.HTTP.Simple as Http
import qualified Data.Aeson as A
import qualified Data.ByteString.Lazy as BS
import Data.Aeson (FromJSON (..), ToJSON (..), (.:), (.:?), (.=), (.!=), (.:!))
import qualified Data.Text as T
import Control.Exception
import CDP.Internal.Utils
import qualified CDP.Definition
type URL = T.Text
type TargetId = T.Text
data EPBrowserVersion = EPBrowserVersion
data EPAllTargets = EPAllTargets
data EPCurrentProtocol = EPCurrentProtocol
data EPOpenNewTab = EPOpenNewTab { unOpenNewTab :: URL }
data EPActivateTarget = EPActivateTarget { unActivateTarget :: TargetId }
data EPCloseTarget = EPCloseTarget { unCloseTarget :: TargetId }
data EPFrontend = EPFrontend
data SomeEndpoint where
SomeEndpoint :: Endpoint ep => ep -> SomeEndpoint
fromSomeEndpoint :: (forall ep. Endpoint ep => ep -> r) -> SomeEndpoint -> r
fromSomeEndpoint f (SomeEndpoint ep) = f ep
-- | Sends a request with the given parameters to the corresponding endpoint
endpoint :: Endpoint ep => Config -> ep -> IO (EndpointResponse ep)
endpoint = getEndpoint . hostPort
-- | Creates a session with a new tab
connectToTab :: Config -> URL -> IO TargetInfo
connectToTab cfg url = do
targetInfo <- endpoint cfg $ EPOpenNewTab url
endpoint cfg $ EPActivateTarget $ tiId targetInfo
pure targetInfo
class Endpoint ep where
type EndpointResponse ep :: *
getEndpoint :: (String, Int) -> ep -> IO (EndpointResponse ep)
epDecode :: Proxy ep -> BS.ByteString -> Either String (EndpointResponse ep)
instance Endpoint EPBrowserVersion where
type EndpointResponse EPBrowserVersion = BrowserVersion
getEndpoint hostPort _ = performRequest (Proxy :: Proxy EPBrowserVersion) $
getRequest hostPort ["json", "version"] Nothing
epDecode = const A.eitherDecode
instance Endpoint EPAllTargets where
type EndpointResponse EPAllTargets = [TargetInfo]
getEndpoint hostPort _ = performRequest (Proxy :: Proxy EPAllTargets) $
getRequest hostPort ["json", "list"] Nothing
epDecode = const A.eitherDecode
instance Endpoint EPCurrentProtocol where
type EndpointResponse EPCurrentProtocol = CDP.Definition.TopLevel
getEndpoint hostPort _ = performRequest (Proxy :: Proxy EPCurrentProtocol) $
getRequest hostPort ["json", "protocol"] Nothing
epDecode = const A.eitherDecode
instance Endpoint EPOpenNewTab where
type EndpointResponse EPOpenNewTab = TargetInfo
getEndpoint hostPort (EPOpenNewTab url) = performRequest (Proxy :: Proxy EPOpenNewTab) $
getRequest hostPort ["json", "new"] (Just url)
epDecode = const A.eitherDecode
instance Endpoint EPActivateTarget where
type EndpointResponse EPActivateTarget = ()
getEndpoint hostPort (EPActivateTarget id) = performRequest (Proxy :: Proxy EPActivateTarget) $
getRequest hostPort ["json", "activate", id] Nothing
epDecode = const . const $ Right ()
instance Endpoint EPCloseTarget where
type EndpointResponse EPCloseTarget = ()
getEndpoint hostPort (EPCloseTarget id) = performRequest (Proxy :: Proxy EPCloseTarget) $
getRequest hostPort ["json", "close", id] Nothing
epDecode = const . const $ Right ()
instance Endpoint EPFrontend where
type EndpointResponse EPFrontend = BS.ByteString
getEndpoint hostPort EPFrontend = performRequest (Proxy :: Proxy EPFrontend) $
getRequest hostPort ["devtools", "inspector.html"] Nothing
epDecode = const Right
data BrowserVersion = BrowserVersion
{ bvBrowser :: T.Text
, bvProtocolVersion :: T.Text
, bvUserAgent :: T.Text
, bvV8Version :: T.Text
, bvVebKitVersion :: T.Text
, bvWebSocketDebuggerUrl :: T.Text
} deriving (Show, Eq)
instance FromJSON BrowserVersion where
parseJSON = A.withObject "BrowserVersion" $ \v ->
BrowserVersion <$> v .: "Browser"
<*> v .: "Protocol-Version"
<*> v .: "User-Agent"
<*> v .: "V8-Version"
<*> v .: "WebKit-Version"
<*> v .: "webSocketDebuggerUrl"
data TargetInfo = TargetInfo
{ tiDescription :: T.Text
, tiDevtoolsFrontendUrl :: T.Text
, tiId :: T.Text
, tiTitle :: T.Text
, tiType :: T.Text
, tiUrl :: T.Text
, tiWebSocketDebuggerUrl :: T.Text
} deriving Show
instance FromJSON TargetInfo where
parseJSON = A.withObject "TargetInfo" $ \v ->
TargetInfo <$> v .: "description"
<*> v .: "devtoolsFrontendUrl"
<*> v .: "id"
<*> v .: "title"
<*> v .: "type"
<*> v .: "url"
<*> v .: "webSocketDebuggerUrl"
browserAddress :: (String, Int) -> IO (String, Int, String)
browserAddress hostPort = fromMaybe (throw . ERRParse $ "invalid URI when connecting to browser") .
parseUri . T.unpack . bvWebSocketDebuggerUrl <$> getEndpoint hostPort EPBrowserVersion
getRequest :: (String, Int) -> [T.Text] -> Maybe T.Text -> Http.Request
getRequest (host, port) path mbParam = Http.parseRequest_ . T.unpack $ r
where
r = mconcat ["GET ", T.pack host, ":", T.pack (show port), "/", T.intercalate "/" path
, maybe "" ("?" <>) mbParam
]
performRequest :: Endpoint ep => Proxy ep -> Http.Request -> IO (EndpointResponse ep)
performRequest p req = do
body <- Http.getResponseBody <$> Http.httpLBS req
either (throwIO . ERRParse) pure $ epDecode p body
parseUri :: String -> Maybe (String, Int, String)
parseUri uri = do
u <- Uri.parseURI $ uri
auth <- Uri.uriAuthority u
let port = case Uri.uriPort auth of
(':':str) -> read str
_ -> 80
pure (Uri.uriRegName auth, port, Uri.uriPath u)