@@ -9,6 +9,7 @@
9{-# LANGUAGE GADTs #-}
9{-# LANGUAGE GADTs #-}
10{-# LANGUAGE GeneralizedNewtypeDeriving #-}
10{-# LANGUAGE GeneralizedNewtypeDeriving #-}
11{-# LANGUAGE JavaScriptFFI #-}
11{-# LANGUAGE JavaScriptFFI #-}
12+{-# LANGUAGE LambdaCase #-}
12{-# LANGUAGE MultiParamTypeClasses #-}
13{-# LANGUAGE MultiParamTypeClasses #-}
13{-# LANGUAGE NoMonomorphismRestriction #-}
14{-# LANGUAGE NoMonomorphismRestriction #-}
14{-# LANGUAGE PolyKinds #-}
15{-# LANGUAGE PolyKinds #-}
@@ -35,19 +36,38 @@ import Reflex.TriggerEvent.Class
36import Control.Concurrent
37import Control.Concurrent
37import Control.Concurrent.STM
38import Control.Concurrent.STM
38−import Control.Exception (SomeException)
39+import Control.Exception
39import Control.Lens
40import Control.Lens
40import Control.Monad hiding (forM, forM_, mapM, mapM_, sequence)
41import Control.Monad hiding (forM, forM_, mapM, mapM_, sequence)
41import Control.Monad.IO.Class
42import Control.Monad.IO.Class
42import Control.Monad.State
43import Control.Monad.State
44+import Data.Aeson hiding (json)
43import Data.ByteString (ByteString)
45import Data.ByteString (ByteString)
44import Data.Default
46import Data.Default
45import Data.IORef
47import Data.IORef
48+import Data.JSString (JSString)
46import Data.Maybe (isJust)
49import Data.Maybe (isJust)
47import Data.Text
50import Data.Text
51+import Data.Text.Encoding
48import GHCJS.DOM.Types (runJSM, askJSM, MonadJSM, liftJSM, JSM)
52import GHCJS.DOM.Types (runJSM, askJSM, MonadJSM, liftJSM, JSM)
54+import qualified Language.Javascript.JSaddle as JS (jsg, js1)
49import qualified Language.Javascript.JSaddle.Monad as JS (catch)
55import qualified Language.Javascript.JSaddle.Monad as JS (catch)
58+import GHCJS.DOM.Types ()
59+import System.IO.Unsafe
61+import Data.JSString.Text
62+import qualified Data.ByteString.Lazy as LBS
64+-- When on ghcjs and using packages with jsstring patches it removes the FromJSVal
65+-- instance for Value from ghcjs-base. This makes it so that if the patches
66+-- are used then the instance comes from ghcjs-json
67+#if defined(ghcjs_HOST_OS) && defined(USE_TEXT_JSSTRING)
68+import JavaScript.JSON.Types.FromJSVal ()
51data WebSocketConfig t a
71data WebSocketConfig t a
52 = WebSocketConfig { _webSocketConfig_send :: Event t [a]
72 = WebSocketConfig { _webSocketConfig_send :: Event t [a]
53 , _webSocketConfig_close :: Event t (Word, Text)
73 , _webSocketConfig_close :: Event t (Word, Text)
@@ -122,6 +142,32 @@ webSocket' url config onRawMessage = do
122 unless success $ atomically $ unGetTQueue payloadQueue payload
142 unless success $ atomically $ unGetTQueue payloadQueue payload
123 return $ RawWebSocket eRecv eOpen eError eClose
143 return $ RawWebSocket eRecv eOpen eError eClose
145+textWebSocket :: (MonadJSM m, MonadJSM (Performable m), HasJSContext m, PostBuild t m, TriggerEvent t m, PerformEvent t m, MonadHold t m, Reflex t) => Text -> WebSocketConfig t Text -> m (RawWebSocket t Text)
146+textWebSocket url cfg = webSocket' url cfg (either (return . decodeUtf8) fromJSValUnchecked)
148+-- | Deserialize a JSON value from a JSString.
149+-- When on ghcjs it uses JSON.parse which is usually faster than using aeson's decode to get a Value
150+-- When on ghc the JSString is converted to Text and then to ByteString in order to use aeson's decode
151+jsonDecode :: FromJSON a => JSString -> Maybe a
154+ result <- unsafePerformIO $ handle (\(_ :: SomeException) -> pure Nothing) $ do
155+ safeJsonParse t >>= \case
156+ Nothing -> pure Nothing
157+ Just a -> fromJSVal a
158+ case fromJSON result of
162+ decode $ LBS.fromStrict $ encodeUtf8 $ textFromJSString t
165+safeJsonParse :: JSString -> JSM (Maybe JSVal)
166+safeJsonParse a = (Just <$> jsonParse a) `JS.catch` \(_ :: SomeException) -> return Nothing
168+jsonParse :: JSString -> JSM JSVal
169+jsonParse a = JS.jsg "JSON" ^. JS.js1 "parse" a
125#ifdef USE_TEMPLATE_HASKELL
171#ifdef USE_TEMPLATE_HASKELL
126makeLensesWith (lensRules & simpleLenses .~ True) ''WebSocketConfig
172makeLensesWith (lensRules & simpleLenses .~ True) ''WebSocketConfig
127makeLensesWith (lensRules & simpleLenses .~ True) ''RawWebSocket
173makeLensesWith (lensRules & simpleLenses .~ True) ''RawWebSocket