Skip to content

Commit 046aac0

Browse files
committed
build solga-client-ghcjs with normal ghc too
1 parent 8b761dc commit 046aac0

File tree

3 files changed

+122
-35
lines changed

3 files changed

+122
-35
lines changed

solga-client-ghcjs/solga-client-ghcjs.cabal

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,23 @@ library
1818
exposed-modules: Solga.Client.GHCJS
1919
build-depends: base >= 4.8 && < 5,
2020
solga-core,
21-
ghcjs-base,
22-
jsval-json,
2321
dlist,
2422
ghcjs-dom
23+
if !impl(ghcjs)
24+
build-depends:
25+
aeson,
26+
uri-encode,
27+
text,
28+
bytestring,
29+
jsaddle,
30+
exceptions,
31+
transformers
32+
else
33+
build-depends:
34+
ghcjs-base,
35+
jsval-json
2536
hs-source-dirs: src
2637
default-language: Haskell2010
2738
ghc-options: -Wall
28-
js-sources: jsbits/xhr.js
39+
if impl(ghcjs)
40+
js-sources: jsbits/xhr.js

solga-client-ghcjs/src/Solga/Client/GHCJS.hs

Lines changed: 96 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
{-# LANGUAGE DeriveFoldable #-}
2424
{-# LANGUAGE DeriveTraversable #-}
2525
{-# LANGUAGE NamedFieldPuns #-}
26+
{-# LANGUAGE CPP #-}
2627
module Solga.Client.GHCJS
2728
( Client(..)
2829
, SomeRequestData(..)
@@ -42,28 +43,47 @@ import Data.Proxy
4243
import GHC.Generics
4344
import GHC.TypeLits (symbolVal, KnownSymbol, Symbol)
4445
import Data.Monoid ((<>))
45-
import qualified Data.JSString as JSS
46-
import Data.JSString (JSString)
4746
import qualified Data.DList as DList
4847
import Data.DList (DList)
4948
import Data.String (fromString)
50-
import qualified JavaScript.JSValJSON as Json
5149
import qualified GHCJS.DOM.XMLHttpRequest as DOM
5250
import qualified GHCJS.DOM.Types as DOM
5351
import qualified GHCJS.DOM.Enums as DOM
5452
import Data.Foldable (for_)
53+
import Control.Monad.IO.Class (liftIO)
5554

5655
import 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

6080
data 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

7494
data 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-
8096
class 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

90106
choose :: forall in_ out a.
91107
(Client in_, RequestData out ~ SomeRequestData out)
92108
=> (out -> in_) -> RequestData in_ a -> RequestData out a
93109
choose _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

97113
instance 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
110126
addSegment req seg = req{reqSegments = reqSegments req <> DList.singleton seg}
111127

112128
instance (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

139155
class 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

145161
data WithData a next b = WithData
@@ -155,7 +171,7 @@ instance (Client next, ToSegment a) => Client (Capture a next) where
155171
instance (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

160176
data 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)
172189
foreign import javascript interruptible
173190
"h$solgaSendXHR($1, null, $c);"
174191
js_send0 :: DOM.XMLHttpRequest -> IO Int
175192
foreign 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))
180201
performXHR 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

215269
instance (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+
222286
instance (Client next) => Client (WithIO next) where
223287
type RequestData (WithIO next) = RequestData next
224288
performRequest _p req perf = performRequest (Proxy @next) req perf

stack.yaml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,21 @@ packages:
1010
- 'solga-core'
1111
- 'solga-router'
1212
- 'solga-client'
13+
- 'solga-client-ghcjs'
14+
- location:
15+
git: https://github.com/bitonic/jsaddle.git
16+
commit: 40b17863a3d4de7346e80937931cc04c8b4b3cd6
17+
subdirs:
18+
- jsaddle
19+
extra-dep: true
1320

1421
# Packages to be pulled from upstream that are not in the resolver (e.g., acme-missiles-0.3)
1522
extra-deps:
1623
- aeson-1.2.1.0
24+
- ghcjs-dom-0.9.2.0
25+
- ghcjs-dom-jsaddle-0.9.2.0
26+
- jsaddle-dom-0.9.2.0
27+
- ref-tf-0.4.0.1
1728

1829
# Override default flag values for local packages and extra-deps
1930
flags: {}

0 commit comments

Comments
 (0)