Skip to content

Commit 4f1a86d

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

File tree

3 files changed

+126
-35
lines changed

3 files changed

+126
-35
lines changed

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

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,24 @@ 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+
jsaddle-dom,
31+
exceptions,
32+
transformers
33+
else
34+
build-depends:
35+
ghcjs-base,
36+
jsval-json
2537
hs-source-dirs: src
2638
default-language: Haskell2010
2739
ghc-options: -Wall
28-
js-sources: jsbits/xhr.js
40+
if impl(ghcjs)
41+
js-sources: jsbits/xhr.js

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

Lines changed: 99 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,48 @@ 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 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

6081
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
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

7495
data 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-
8097
class 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

90107
choose :: forall in_ out a.
91108
(Client in_, RequestData out ~ SomeRequestData out)
92109
=> (out -> in_) -> RequestData in_ a -> RequestData out a
93110
choose _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

97114
instance 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
110127
addSegment req seg = req{reqSegments = reqSegments req <> DList.singleton seg}
111128

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

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

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

160177
data 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)
172190
foreign import javascript interruptible
173191
"h$solgaSendXHR($1, null, $c);"
174192
js_send0 :: DOM.XMLHttpRequest -> IO Int
175193
foreign 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))
180202
performXHR 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

215272
instance (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+
222289
instance (Client next) => Client (WithIO next) where
223290
type RequestData (WithIO next) = RequestData next
224291
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)