2323{-# LANGUAGE DeriveFoldable #-}
2424{-# LANGUAGE DeriveTraversable #-}
2525{-# LANGUAGE NamedFieldPuns #-}
26+ {-# LANGUAGE CPP #-}
2627module Solga.Client.GHCJS
2728 ( Client (.. )
2829 , SomeRequestData (.. )
@@ -42,28 +43,47 @@ 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 Control.Monad.Catch (bracket )
74+ import Control.Monad (void )
75+ import Control.Monad.Trans.Class (lift )
76+ #endif
77+
78+ type Header = (Text , Text )
5979
6080data 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
81+ { reqMethod :: Text
82+ , reqHost :: Text
83+ , reqSegments :: DList Text
84+ , reqQueryString :: Text
85+ , reqUser :: Maybe Text
86+ , reqPassword :: Maybe Text
6787 , reqHeaders :: [Header ]
6888 , reqBody :: Maybe body
6989 , reqXHR :: DOM. XMLHttpRequest
@@ -73,26 +93,22 @@ data Request = forall body. (DOM.IsXMLHttpRequestBody body) => Request
7393
7494data SomeRequestData out a = forall in_ . (Client in_ ) => SomeRequestData (Proxy in_ ) (RequestData in_ a )
7595
76- foreign import javascript unsafe
77- " encodeURI($1)"
78- js_encodeURI :: JSString -> IO JSString
79-
8096class Client r where
8197 type RequestData r :: * -> *
8298 type RequestData r = SomeRequestData r
83- performRequest :: proxy r -> Request -> RequestData r a -> IO a
99+ performRequest :: proxy r -> Request -> RequestData r a -> DOM. JSM a
84100 default
85101 performRequest :: forall (proxy :: * -> * ) a .
86102 (RequestData r ~ SomeRequestData r )
87- => proxy r -> Request -> RequestData r a -> IO a
103+ => proxy r -> Request -> RequestData r a -> DOM. JSM a
88104 performRequest _p req (SomeRequestData p perf) = performRequest p req perf
89105
90106choose :: forall in_ out a .
91107 (Client in_ , RequestData out ~ SomeRequestData out )
92108 => (out -> in_ ) -> RequestData in_ a -> RequestData out a
93109choose _f perf = SomeRequestData (Proxy @ in_ ) perf
94110
95- newtype RawRequest a = RawRequest { unRequestDataRaw :: Request -> IO a }
111+ newtype RawRequest a = RawRequest { unRequestDataRaw :: Request -> DOM. JSM a }
96112
97113instance Client (Raw a ) where
98114 type RequestData (Raw a ) = RawRequest
@@ -106,7 +122,7 @@ instance (Client next) => Client (End next) where
106122 type RequestData (End next ) = RequestData next
107123 performRequest _p req perf = performRequest (Proxy @ next ) req perf
108124
109- addSegment :: Request -> JSString -> Request
125+ addSegment :: Request -> Text -> Request
110126addSegment req seg = req{reqSegments = reqSegments req <> DList. singleton seg}
111127
112128instance (Client next , KnownSymbol seg ) => Client (Seg seg next ) where
@@ -137,9 +153,9 @@ instance (Client next) => Client (OneOfSegs segs next) where
137153 performRequest (Proxy @ next ) (addSegment req (fromString (whichSeg ws))) perf
138154
139155class ToSegment a where
140- toSegment :: a -> JSString
156+ toSegment :: a -> Text
141157
142- instance ToSegment JSString where
158+ instance ToSegment Text where
143159 toSegment = id
144160
145161data WithData a next b = WithData
@@ -155,7 +171,7 @@ instance (Client next, ToSegment a) => Client (Capture a next) where
155171instance (Client next , KnownSymbol method ) => Client (Method method next ) where
156172 type RequestData (Method seg next ) = RequestData next
157173 performRequest _p req perf = performRequest
158- (Proxy @ next ) req{reqMethod = JSS . pack (symbolVal (Proxy @ method ))} perf
174+ (Proxy @ next ) req{reqMethod = T . pack (symbolVal (Proxy @ method ))} perf
159175
160176data Response a = Response
161177 { responseStatus :: Word
@@ -167,20 +183,25 @@ data XHRError =
167183 | XHRError
168184 deriving (Eq , Show )
169185
170- newtype GetResponse a b = GetResponse { unGetResponse :: Either XHRError (Response a ) -> IO b }
186+ newtype GetResponse a b = GetResponse { unGetResponse :: Either XHRError (Response a ) -> DOM. JSM b }
171187
188+ #if defined(ghcjs_HOST_OS)
172189foreign import javascript interruptible
173190 " h$solgaSendXHR($1, null, $c);"
174191 js_send0 :: DOM. XMLHttpRequest -> IO Int
175192foreign import javascript interruptible
176193 " h$solgaSendXHR($1, $2, $c);"
177- js_send1 :: DOM. XMLHttpRequest -> DOM. JSVal -> IO Int
194+ js_send1 :: DOM. XMLHttpRequest -> DOM. JSVal -> IO Int
195+
196+ foreign import javascript unsafe
197+ " encodeURI($1)"
198+ js_encodeURI :: Text -> IO Text
178199
179- performXHR :: DOM. XMLHttpRequestResponseType -> Request -> IO (Either XHRError (Response DOM. JSVal ))
200+ performXHR :: DOM. XMLHttpRequestResponseType -> Request -> DOM. JSM (Either XHRError (Response DOM. JSVal ))
180201performXHR respType Request {.. } = do
181202 let xhr = reqXHR
182203 DOM. setResponseType xhr respType
183- uri <- js_encodeURI (reqHost <> " /" <> JSS . intercalate " /" (DList. toList reqSegments) <> reqQueryString)
204+ uri <- liftIO ( js_encodeURI (reqHost <> " /" <> T . intercalate " /" (DList. toList reqSegments) <> reqQueryString) )
184205 DOM. open xhr reqMethod uri True reqUser reqPassword
185206 for_ reqHeaders (uncurry (DOM. setRequestHeader xhr))
186207 r <- case reqBody of
@@ -204,21 +225,64 @@ instance (Json.FromJSON a) => Client (JSON a) where
204225 resp <- performXHR DOM. XMLHttpRequestResponseTypeJson req
205226 f (fmap (fmap (Json. runParser Json. parseJSON)) resp)
206227
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
228+ instance (Client next , Json. ToJSON a ) => Client (ReqBodyJSON a next ) where
229+ type RequestData (ReqBodyJSON a next ) = WithData a (RequestData next )
230+ performRequest _p Request {.. } (WithData x perf) = do
231+ s <- Json. toJSONString =<< Json. toJSON x
232+ performRequest
233+ (Proxy @ next ) Request {reqBody = Just s, .. } perf
210234
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
235+ #else
236+
237+ performXHR :: DOM. XMLHttpRequestResponseType -> Request -> DOM. JSM (Either XHRError (Response Text ))
238+ performXHR respType Request {.. } = do
239+ let xhr = reqXHR
240+ DOM. setResponseType xhr respType
241+ let uri = Uri. encodeText (reqHost <> " /" <> T. intercalate " /" (DList. toList reqSegments) <> reqQueryString)
242+ DOM. open xhr reqMethod uri True reqUser reqPassword
243+ for_ reqHeaders (uncurry (DOM. setRequestHeader xhr))
244+ result :: MVar (Either XHRError (Response Text )) <- liftIO newEmptyMVar
245+ let onLoad = lift $ do
246+ status <- DOM. getStatus xhr
247+ resp <- DOM. getResponseTextUnchecked xhr
248+ void (liftIO (tryPutMVar result (Right (Response status resp))))
249+ bracket
250+ (DOM.Event. on xhr DOM.Event. error (liftIO (void (tryPutMVar result (Left XHRError )))))
251+ id
252+ (\ _ -> bracket
253+ (DOM.Event. on xhr DOM.Event. abortEvent (liftIO (void (tryPutMVar result (Left XHRAborted )))))
254+ id
255+ (\ _ -> bracket
256+ (DOM.Event. on xhr DOM.Event. load onLoad)
257+ id
258+ (\ _ -> liftIO (takeMVar result))))
259+
260+ instance (Json. FromJSON a ) => Client (JSON a ) where
261+ -- note that we do not decode eagerly because it's often the case that the body
262+ -- cannot be decoded since web servers return invalid json on errors
263+ -- (e.g. "Internal server error" on a 500 rather than a json encoded error)
264+ type RequestData (JSON a ) = GetResponse (IO (Either String a ))
265+ performRequest _p req (GetResponse f) = do
266+ resp <- performXHR DOM. XMLHttpRequestResponseTypeText req
267+ f (fmap (fmap (return . Json. eitherDecode . BSL. fromStrict . T. encodeUtf8)) resp)
214268
215269instance (Client next , Json. ToJSON a ) => Client (ReqBodyJSON a next ) where
216270 type RequestData (ReqBodyJSON a next ) = WithData a (RequestData next )
217271 performRequest _p Request {.. } (WithData x perf) = do
218- s <- Json. toJSONString =<< Json. toJSON x
272+ let s = JSaddle. toJSString ( T. decodeUtf8 ( BSL. toStrict ( Json. encode x)))
219273 performRequest
220274 (Proxy @ next ) Request {reqBody = Just s, .. } perf
221275
276+ #endif
277+
278+ instance (Client next ) => Client (ExtraHeaders next ) where
279+ type RequestData (ExtraHeaders next ) = RequestData next
280+ performRequest _p req perf = performRequest (Proxy @ next ) req perf
281+
282+ instance (Client next ) => Client (NoCache next ) where
283+ type RequestData (NoCache next ) = RequestData next
284+ performRequest _p req perf = performRequest (Proxy @ next ) req perf
285+
222286instance (Client next ) => Client (WithIO next ) where
223287 type RequestData (WithIO next ) = RequestData next
224288 performRequest _p req perf = performRequest (Proxy @ next ) req perf
0 commit comments