2323{-# LANGUAGE DeriveFoldable #-}
2424{-# LANGUAGE DeriveTraversable #-}
2525{-# LANGUAGE NamedFieldPuns #-}
26+ {-# LANGUAGE CPP #-}
2627module Solga.Client.GHCJS
2728 ( Client (.. )
2829 , SomeRequestData (.. )
@@ -42,28 +43,48 @@ import Data.Proxy
4243import GHC.Generics
4344import GHC.TypeLits (symbolVal , KnownSymbol , Symbol )
4445import Data.Monoid ((<>) )
45- import qualified Data.JSString as JSS
46- import Data.JSString (JSString )
4746import qualified Data.DList as DList
4847import Data.DList (DList )
4948import Data.String (fromString )
50- import qualified JavaScript.JSValJSON as Json
5149import qualified GHCJS.DOM.XMLHttpRequest as DOM
5250import qualified GHCJS.DOM.Types as DOM
5351import qualified GHCJS.DOM.Enums as DOM
5452import Data.Foldable (for_ )
53+ import Control.Monad.IO.Class (liftIO )
5554
5655import Solga.Core hiding (Header )
5756
58- type Header = (JSString , JSString )
57+ #if defined(ghcjs_HOST_OS)
58+ import qualified JavaScript.JSValJSON as Json
59+ import Data.JSString (JSString )
60+ import qualified Data.JSString as T
61+ type Text = JSString
62+ #else
63+ import qualified Data.Aeson as Json
64+ import Data.Text (Text )
65+ import qualified Data.Text as T
66+ import qualified Data.Text.Encoding as T
67+ import qualified Network.URI.Encode as Uri
68+ import qualified Data.ByteString.Lazy as BSL
69+ import qualified Language.Javascript.JSaddle as JSaddle
70+ import Control.Concurrent.MVar (takeMVar , tryPutMVar , MVar , newEmptyMVar )
71+ import qualified GHCJS.DOM.EventM as DOM.Event
72+ import qualified GHCJS.DOM.XMLHttpRequestEventTarget as DOM.Event
73+ import qualified JSDOM.Generated.XMLHttpRequest as DOM.XMLHttpRequest
74+ import Control.Monad.Catch (bracket )
75+ import Control.Monad (void )
76+ import Control.Monad.Trans.Class (lift )
77+ #endif
78+
79+ type Header = (Text , Text )
5980
6081data Request = forall body . (DOM. IsXMLHttpRequestBody body ) => Request
61- { reqMethod :: JSString
62- , reqHost :: JSString
63- , reqSegments :: DList JSString
64- , reqQueryString :: JSString
65- , reqUser :: Maybe JSString
66- , reqPassword :: Maybe JSString
82+ { reqMethod :: Text
83+ , reqHost :: Text
84+ , reqSegments :: DList Text
85+ , reqQueryString :: Text
86+ , reqUser :: Maybe Text
87+ , reqPassword :: Maybe Text
6788 , reqHeaders :: [Header ]
6889 , reqBody :: Maybe body
6990 , reqXHR :: DOM. XMLHttpRequest
@@ -73,26 +94,22 @@ data Request = forall body. (DOM.IsXMLHttpRequestBody body) => Request
7394
7495data SomeRequestData out a = forall in_ . (Client in_ ) => SomeRequestData (Proxy in_ ) (RequestData in_ a )
7596
76- foreign import javascript unsafe
77- " encodeURI($1)"
78- js_encodeURI :: JSString -> IO JSString
79-
8097class Client r where
8198 type RequestData r :: * -> *
8299 type RequestData r = SomeRequestData r
83- performRequest :: proxy r -> Request -> RequestData r a -> IO a
100+ performRequest :: proxy r -> Request -> RequestData r a -> DOM. JSM a
84101 default
85102 performRequest :: forall (proxy :: * -> * ) a .
86103 (RequestData r ~ SomeRequestData r )
87- => proxy r -> Request -> RequestData r a -> IO a
104+ => proxy r -> Request -> RequestData r a -> DOM. JSM a
88105 performRequest _p req (SomeRequestData p perf) = performRequest p req perf
89106
90107choose :: forall in_ out a .
91108 (Client in_ , RequestData out ~ SomeRequestData out )
92109 => (out -> in_ ) -> RequestData in_ a -> RequestData out a
93110choose _f perf = SomeRequestData (Proxy @ in_ ) perf
94111
95- newtype RawRequest a = RawRequest { unRequestDataRaw :: Request -> IO a }
112+ newtype RawRequest a = RawRequest { unRequestDataRaw :: Request -> DOM. JSM a }
96113
97114instance Client (Raw a ) where
98115 type RequestData (Raw a ) = RawRequest
@@ -106,7 +123,7 @@ instance (Client next) => Client (End next) where
106123 type RequestData (End next ) = RequestData next
107124 performRequest _p req perf = performRequest (Proxy @ next ) req perf
108125
109- addSegment :: Request -> JSString -> Request
126+ addSegment :: Request -> Text -> Request
110127addSegment req seg = req{reqSegments = reqSegments req <> DList. singleton seg}
111128
112129instance (Client next , KnownSymbol seg ) => Client (Seg seg next ) where
@@ -137,9 +154,9 @@ instance (Client next) => Client (OneOfSegs segs next) where
137154 performRequest (Proxy @ next ) (addSegment req (fromString (whichSeg ws))) perf
138155
139156class ToSegment a where
140- toSegment :: a -> JSString
157+ toSegment :: a -> Text
141158
142- instance ToSegment JSString where
159+ instance ToSegment Text where
143160 toSegment = id
144161
145162data WithData a next b = WithData
@@ -155,7 +172,7 @@ instance (Client next, ToSegment a) => Client (Capture a next) where
155172instance (Client next , KnownSymbol method ) => Client (Method method next ) where
156173 type RequestData (Method seg next ) = RequestData next
157174 performRequest _p req perf = performRequest
158- (Proxy @ next ) req{reqMethod = JSS . pack (symbolVal (Proxy @ method ))} perf
175+ (Proxy @ next ) req{reqMethod = T . pack (symbolVal (Proxy @ method ))} perf
159176
160177data Response a = Response
161178 { responseStatus :: Word
@@ -167,20 +184,25 @@ data XHRError =
167184 | XHRError
168185 deriving (Eq , Show )
169186
170- newtype GetResponse a b = GetResponse { unGetResponse :: Either XHRError (Response a ) -> IO b }
187+ newtype GetResponse a b = GetResponse { unGetResponse :: Either XHRError (Response a ) -> DOM. JSM b }
171188
189+ #if defined(ghcjs_HOST_OS)
172190foreign import javascript interruptible
173191 " h$solgaSendXHR($1, null, $c);"
174192 js_send0 :: DOM. XMLHttpRequest -> IO Int
175193foreign import javascript interruptible
176194 " h$solgaSendXHR($1, $2, $c);"
177- js_send1 :: DOM. XMLHttpRequest -> DOM. JSVal -> IO Int
195+ js_send1 :: DOM. XMLHttpRequest -> DOM. JSVal -> IO Int
178196
179- performXHR :: DOM. XMLHttpRequestResponseType -> Request -> IO (Either XHRError (Response DOM. JSVal ))
197+ foreign import javascript unsafe
198+ " encodeURI($1)"
199+ js_encodeURI :: Text -> IO Text
200+
201+ performXHR :: DOM. XMLHttpRequestResponseType -> Request -> DOM. JSM (Either XHRError (Response DOM. JSVal ))
180202performXHR respType Request {.. } = do
181203 let xhr = reqXHR
182204 DOM. setResponseType xhr respType
183- uri <- js_encodeURI (reqHost <> " /" <> JSS . intercalate " /" (DList. toList reqSegments) <> reqQueryString)
205+ uri <- liftIO ( js_encodeURI (reqHost <> " /" <> T . intercalate " /" (DList. toList reqSegments) <> reqQueryString) )
184206 DOM. open xhr reqMethod uri True reqUser reqPassword
185207 for_ reqHeaders (uncurry (DOM. setRequestHeader xhr))
186208 r <- case reqBody of
@@ -204,21 +226,66 @@ instance (Json.FromJSON a) => Client (JSON a) where
204226 resp <- performXHR DOM. XMLHttpRequestResponseTypeJson req
205227 f (fmap (fmap (Json. runParser Json. parseJSON)) resp)
206228
207- instance (Client next ) => Client (ExtraHeaders next ) where
208- type RequestData (ExtraHeaders next ) = RequestData next
209- performRequest _p req perf = performRequest (Proxy @ next ) req perf
229+ instance (Client next , Json. ToJSON a ) => Client (ReqBodyJSON a next ) where
230+ type RequestData (ReqBodyJSON a next ) = WithData a (RequestData next )
231+ performRequest _p Request {.. } (WithData x perf) = do
232+ s <- Json. toJSONString =<< Json. toJSON x
233+ performRequest
234+ (Proxy @ next ) Request {reqBody = Just s, .. } perf
210235
211- instance (Client next ) => Client (NoCache next ) where
212- type RequestData (NoCache next ) = RequestData next
213- performRequest _p req perf = performRequest (Proxy @ next ) req perf
236+ #else
237+
238+ performXHR :: DOM. XMLHttpRequestResponseType -> Request -> DOM. JSM (Either XHRError (Response Text ))
239+ performXHR respType Request {.. } = do
240+ let xhr = reqXHR
241+ DOM. setResponseType xhr respType
242+ let uri = Uri. encodeText (reqHost <> " /" <> T. intercalate " /" (DList. toList reqSegments) <> reqQueryString)
243+ DOM. open xhr reqMethod uri True reqUser reqPassword
244+ for_ reqHeaders (uncurry (DOM. setRequestHeader xhr))
245+ result :: MVar (Either XHRError (Response Text )) <- liftIO newEmptyMVar
246+ let onLoad = lift $ do
247+ status <- DOM. getStatus xhr
248+ resp <- DOM. getResponseTextUnchecked xhr
249+ void (liftIO (tryPutMVar result (Right (Response status resp))))
250+ bracket
251+ (DOM.Event. on xhr DOM.Event. error (liftIO (void (tryPutMVar result (Left XHRError )))))
252+ id
253+ (\ _ -> bracket
254+ (DOM.Event. on xhr DOM.Event. abortEvent (liftIO (void (tryPutMVar result (Left XHRAborted )))))
255+ id
256+ (\ _ -> bracket
257+ (DOM.Event. on xhr DOM.Event. load onLoad)
258+ id
259+ (\ _ -> do
260+ DOM.XMLHttpRequest. send xhr reqBody
261+ liftIO (takeMVar result))))
262+
263+ instance (Json. FromJSON a ) => Client (JSON a ) where
264+ -- note that we do not decode eagerly because it's often the case that the body
265+ -- cannot be decoded since web servers return invalid json on errors
266+ -- (e.g. "Internal server error" on a 500 rather than a json encoded error)
267+ type RequestData (JSON a ) = GetResponse (DOM. JSM (Either String a ))
268+ performRequest _p req (GetResponse f) = do
269+ resp <- performXHR DOM. XMLHttpRequestResponseTypeText req
270+ f (fmap (fmap (return . Json. eitherDecode . BSL. fromStrict . T. encodeUtf8)) resp)
214271
215272instance (Client next , Json. ToJSON a ) => Client (ReqBodyJSON a next ) where
216273 type RequestData (ReqBodyJSON a next ) = WithData a (RequestData next )
217274 performRequest _p Request {.. } (WithData x perf) = do
218- s <- Json. toJSONString =<< Json. toJSON x
275+ let s = JSaddle. toJSString ( T. decodeUtf8 ( BSL. toStrict ( Json. encode x)))
219276 performRequest
220277 (Proxy @ next ) Request {reqBody = Just s, .. } perf
221278
279+ #endif
280+
281+ instance (Client next ) => Client (ExtraHeaders next ) where
282+ type RequestData (ExtraHeaders next ) = RequestData next
283+ performRequest _p req perf = performRequest (Proxy @ next ) req perf
284+
285+ instance (Client next ) => Client (NoCache next ) where
286+ type RequestData (NoCache next ) = RequestData next
287+ performRequest _p req perf = performRequest (Proxy @ next ) req perf
288+
222289instance (Client next ) => Client (WithIO next ) where
223290 type RequestData (WithIO next ) = RequestData next
224291 performRequest _p req perf = performRequest (Proxy @ next ) req perf
0 commit comments