{-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE CPP #-} ----------------------------------------------------------------------------- -- | -- Module : Miso.Effect.XHR -- Copyright : (C) 2016-2017 David M. Johnson -- License : BSD3-style (see the file LICENSE) -- Maintainer : David M. Johnson -- Stability : experimental -- Portability : non-portable ---------------------------------------------------------------------------- module Miso.Effect.XHR where import Data.Proxy import GHC.TypeLits import JavaScript.Web.XMLHttpRequest import Miso.String import Servant.API -- | Still a WIP, use ghcjs-base XHR for now, or other -- | Intermediate type for accumulation data RouteInfo = RouteInfo { riPath :: String , riMethod :: Method } deriving (Show, Eq) -- | Class for `XHR` class HasXHR api where type XHR api :: * xhrWithRoute :: Proxy api -> RouteInfo -> XHR api -- | Result of using `XHR` type Result a = IO (Either MisoString a) -- | Verb instance {-# OVERLAPPABLE #-} ( MimeUnrender ct a , ReflectMethod method , cts' ~ (ct ': cts) ) => HasXHR (Verb method status cts' a) where type XHR (Verb method status cts' a) = Result a xhrWithRoute Proxy _ = undefined -- snd <$> performRequestCT (Proxy :: Proxy ct) method req -- where method = reflectMethod (Proxy :: Proxy method) -- | Verb NoContent instance {-# OVERLAPPING #-} ReflectMethod method => HasXHR (Verb method status cts NoContent) where type XHR (Verb method status cts NoContent) = Result NoContent xhrWithRoute Proxy _ = undefined -- performRequestNoBody method req >> return NoContent -- where method = reflectMethod (Proxy :: Proxy method) -- | Verb, with HEADERS instance {-# OVERLAPPING #-} ( MimeUnrender ct a, BuildHeadersTo ls, ReflectMethod method, cts' ~ (ct ': cts) ) => HasXHR (Verb method status cts' (Headers ls a)) where type XHR (Verb method status cts' (Headers ls a)) = Result (Headers ls a) xhrWithRoute Proxy _ = undefined -- let method = reflectMethod (Proxy :: Proxy method) -- (hdrs, resp) <- performRequestCT (Proxy :: Proxy ct) method req -- return $ Headers { getResponse = resp -- , getHeadersHList = buildHeadersTo hdrs -- } -- | `Headers`, with `NoContent` instance {-# OVERLAPPING #-} ( BuildHeadersTo ls, ReflectMethod method ) => HasXHR (Verb method status cts (Headers ls NoContent)) where type XHR (Verb method status cts (Headers ls NoContent)) = Result (Headers ls NoContent) xhrWithRoute Proxy _ = undefined -- let method = reflectMethod (Proxy :: Proxy method) -- hdrs <- performRequestNoBody method req -- return $ Headers { getResponse = NoContent -- , getHeadersHList = buildHeadersTo hdrs -- } -- | Capture instance (ToHttpApiData a, HasXHR api, KnownSymbol sym) => HasXHR (Capture sym a :> api) where type XHR (Capture sym a :> api) = a -> XHR api xhrWithRoute Proxy _ (_ :: a) = undefined #if MIN_VERSION_servant(0,8,1) -- | CaptureAll instance (KnownSymbol capture, ToHttpApiData a, HasXHR sublayout) => HasXHR (CaptureAll capture a :> sublayout) where type XHR (CaptureAll capture a :> sublayout) = [a] -> XHR sublayout xhrWithRoute Proxy _ _ = undefined -- xhrWithRoute (Proxy :: Proxy sublayout) -- (foldl' (flip appendToPath) req ps) #endif -- | Path (done) instance (HasXHR api, KnownSymbol sym) => HasXHR (sym :> api) where type XHR (sym :> api) = XHR api xhrWithRoute Proxy req = xhrWithRoute (Proxy :: Proxy api) newReq where newReq = req { riPath = riPath req ++ "/" ++ symbolVal (Proxy :: Proxy sym) } -- | Raw (not supported) -- instance HasXHR Raw where -- type XHR Raw = XHR (Response MisoString) -- xhrWithRoute Proxy _ = putStrLn "Raw is not supported" -- | Alternate instance (HasXHR left, HasXHR right) => HasXHR (left :<|> right) where type XHR (left :<|> right) = XHR left :<|> XHR right xhrWithRoute Proxy s = xhrWithRoute (Proxy :: Proxy left) s :<|> xhrWithRoute (Proxy :: Proxy right) s -- | Header instance (ToHttpApiData a, HasXHR api, KnownSymbol sym) => HasXHR (Header sym a :> api) where type XHR (Header sym a :> api) = Maybe a -> XHR api xhrWithRoute Proxy _ = undefined -- | HttpVersion instance HasXHR api => HasXHR (HttpVersion :> api) where type XHR (HttpVersion :> api) = XHR api xhrWithRoute Proxy = xhrWithRoute (Proxy :: Proxy api) -- | Query param instance (KnownSymbol sym, ToHttpApiData a, HasXHR api) => HasXHR (QueryParam sym a :> api) where type XHR (QueryParam sym a :> api) = Maybe a -> XHR api xhrWithRoute Proxy _ _ = undefined -- | Query param(s) instance (KnownSymbol sym, ToHttpApiData a, HasXHR api) => HasXHR (QueryParams sym a :> api) where type XHR (QueryParams sym a :> api) = [a] -> XHR api xhrWithRoute Proxy _ _ = undefined -- | Query flag instance (KnownSymbol sym, HasXHR api) => HasXHR (QueryFlag sym :> api) where type XHR (QueryFlag sym :> api) = Bool -> XHR api xhrWithRoute Proxy _ _ = undefined -- | Request Body instance (MimeRender ct a, HasXHR api) => HasXHR (ReqBody (ct ': cts) a :> api) where type XHR (ReqBody (ct ': cts) a :> api) = a -> XHR api xhrWithRoute Proxy _ _ = undefined -- | Remote host (done) instance HasXHR api => HasXHR (RemoteHost :> api) where type XHR (RemoteHost :> api) = XHR api xhrWithRoute Proxy req = xhrWithRoute (Proxy :: Proxy api) req -- | IsSecure host (done) instance HasXHR api => HasXHR (IsSecure :> api) where type XHR (IsSecure :> api) = XHR api xhrWithRoute Proxy req = xhrWithRoute (Proxy :: Proxy api) req -- | WithNamedContext (done) instance HasXHR api => HasXHR (WithNamedContext :> api) where type XHR (WithNamedContext :> api) = XHR api xhrWithRoute Proxy req = xhrWithRoute (Proxy :: Proxy api) req -- | Vault (done) instance HasXHR api => HasXHR (Vault :> api) where type XHR (Vault :> api) = XHR api xhrWithRoute Proxy req = xhrWithRoute (Proxy :: Proxy api) req -- | BasicAuth instance HasXHR api => HasXHR (BasicAuth realm usr :> api) where type XHR (BasicAuth realm usr :> api) = BasicAuthData -> XHR api xhrWithRoute Proxy _ _ = undefined -- | Can't find AuthenticateReq -- instance HasXHR api => HasXHR (AuthProtect tag :> api) where -- type XHR (AuthProtect tag :> api) = AuthenticateReq (AuthProtect tag) -> XHR api -- xhrWithRoute Proxy req (AuthenticateReq (val,func)) = -- xhrWithRoute (Proxy :: Proxy api) (func val req) -- xhrWithRoute (Proxy :: Proxy api) -- (let ctProxy = Proxy :: Proxy ct -- in setReqBodyLBS (mimeRender ctProxy body) -- -- We use first contentType from the Accept list -- (contentType ctProxy) -- req -- ) -- xhrJSON :: FromJSON json => Request -> IO (Response json) -- xhrJSON req = do -- r <- xhr' req -- case contents r of -- Nothing -> pure r { contents = Just Null } -- Just jsstring -> do -- x <- parse (unsafeCoerce jsstring) -- pure $ r { contents = Just x } -- import Data.JSString -- import GHCJS.Foreign.Callback -- import GHCJS.Nullable -- import GHCJS.Types -- import Prelude hiding (lines) -- data ReadyState -- = UNSENT -- -- ^ XHR has been created. open() not called yet. -- | OPENED -- -- ^ open() has been called. -- | HEADERS_RECEIVED -- -- ^ send() has been called, and headers and status are available. -- | LOADING -- -- ^ Downloading; responseText holds partial data. -- | DONE -- -- ^ The operation is complete. -- deriving (Show, Eq, Enum) -- data ResponseType -- = DOMStringType -- | ArrayBufferType -- | BlobType -- | DocumentType -- | JSONType -- | UnknownXHRType -- deriving (Show, Eq) -- newtype Document = Document JSVal -- newtype XHR = XHR JSVal -- foreign import javascript unsafe "$r = new XMLHttpRequest();" -- newXHR :: IO XHR -- foreign import javascript unsafe "$1.abort();" -- abort :: XHR -> IO () -- foreign import javascript unsafe "$r = $1.responseURL;" -- responseURL :: XHR -> IO JSString -- foreign import javascript unsafe "$r = $1.readyState;" -- readyState' :: XHR -> IO Int -- foreign import javascript unsafe "$r = $1.responseType;" -- responseType' :: XHR -> IO JSString -- responseType :: XHR -> IO ResponseType -- {-# INLINE responseType #-} -- responseType xhr = -- responseType' xhr >>= \case -- "" -> pure DOMStringType -- "blob" -> pure BlobType -- "document" -> pure DocumentType -- "json" -> pure JSONType -- "arraybuffer" -> pure ArrayBufferType -- "text" -> pure DOMStringType -- _ -> pure UnknownXHRType -- readyState :: XHR -> IO ReadyState -- {-# INLINE readyState #-} -- readyState xhr = toEnum <$> readyState' xhr -- -- request.open("GET", "foo.txt", true); -- foreign import javascript unsafe "$1.open($2, $3, $4);" -- open :: XHR -> JSString -> JSString -> Bool -> IO () -- foreign import javascript unsafe "$1.send();" -- send :: XHR -> IO () -- foreign import javascript unsafe "$1.setRequestHeader($2,$3);" -- setRequestHeader :: XHR -> JSString -> JSString -> IO () -- foreign import javascript unsafe "$1.onreadystatechanged = $2;" -- onReadyStateChanged :: XHR -> Callback (JSVal -> IO ()) -> IO () -- foreign import javascript unsafe "$r = $1.getAllResponseHeaders();" -- getAllResponseHeaders' :: XHR -> IO (Nullable JSString) -- foreign import javascript unsafe "$r = $1.getResponseHeader($2);" -- getResponseHeader' :: XHR -> JSString -> IO (Nullable JSString) -- getResponseHeader :: XHR -> JSString -> IO (Maybe JSString) -- {-# INLINE getResponseHeader #-} -- getResponseHeader xhr key = -- nullableToMaybe <$> getResponseHeader' xhr key -- foreign import javascript unsafe "$r = $1.response;" -- response' :: XHR -> IO (Nullable JSVal) -- foreign import javascript unsafe "$r = $1.status;" -- status' :: XHR -> IO (Nullable Int) -- foreign import javascript unsafe "$r = $1.statusText;" -- statusText' :: XHR -> IO (Nullable JSString) -- foreign import javascript unsafe "$1.overrideMimeType($2);" -- overrideMimeType :: XHR -> JSString -> IO () -- foreign import javascript unsafe "$r = $1.timeout;" -- timeout :: XHR -> IO Int -- foreign import javascript unsafe "$1.withCredentials = true;" -- withCredentials :: XHR -> IO () -- foreign import javascript unsafe "$1.response ? true : false" -- hasResponse :: XHR -> IO Bool -- getAllResponseHeaders :: XHR -> IO (Maybe [JSString]) -- {-# INLINE getAllResponseHeaders #-} -- getAllResponseHeaders = \xhr -> do -- result <- getAllResponseHeaders' xhr -- pure $ lines <$> nullableToMaybe result