@@ -3,7 +3,7 @@
3{-# LANGUAGE OverloadedStrings #-}
3{-# LANGUAGE OverloadedStrings #-}
4-----------------------------------------------------------------------------
4-----------------------------------------------------------------------------
6−-- Module : Language.Javascript.JSaddle.WebSockets
6+-- Module : Language.Javascript.JSaddle.Run
7-- Copyright : (c) Hamish Mackenzie
7-- Copyright : (c) Hamish Mackenzie
8-- License : MIT
8-- License : MIT
@@ -13,16 +13,15 @@
14-----------------------------------------------------------------------------
14-----------------------------------------------------------------------------
16−module Language.Javascript.JSaddle.WebSockets (
16+module Language.Javascript.JSaddle.Run (
17− -- * Running JSM over WebSockets
17+ -- * Running JSM
19 , syncPoint
19 , syncPoint
20 , syncAfter
20 , syncAfter
21 , waitForAnimationFrame
21 , waitForAnimationFrame
22 , nextAnimationFrame
22 , nextAnimationFrame
23#ifndef ghcjs_HOST_OS
23#ifndef ghcjs_HOST_OS
25− -- * Functions used to implement JSaddle over WebSockets
24+ -- * Functions used to implement JSaddle using JSON messaging
26 , AsyncCommand(..)
25 , AsyncCommand(..)
27 , Command(..)
26 , Command(..)
28 , Result(..)
27 , Result(..)
@@ -53,40 +52,13 @@ import Control.Concurrent.MVar
53import Data.Monoid ((<>))
52import Data.Monoid ((<>))
54import qualified Data.Text as T (unpack)
53import qualified Data.Text as T (unpack)
55import qualified Data.Map as M (lookup, delete, insert, empty)
54import qualified Data.Map as M (lookup, delete, insert, empty)
56−import Data.Aeson (encode, decode)
57import Data.Time.Clock (getCurrentTime,diffUTCTime)
55import Data.Time.Clock (getCurrentTime,diffUTCTime)
59−import Network.Wai (Application)
60−import Network.Wai.Handler.Warp
61− (defaultSettings, setTimeout, setPort, runSettings)
62−import Network.WebSockets
63− (ConnectionOptions(..), sendTextData, receiveDataMessage,
64− acceptRequest, defaultConnectionOptions, ServerApp)
65−import qualified Network.WebSockets as WS (DataMessage(..))
66−import Network.Wai.Handler.WebSockets (websocketsOr)
68import Language.Javascript.JSaddle.Types
57import Language.Javascript.JSaddle.Types
69 (Command(..), AsyncCommand(..), Result(..), JSContextRef(..), JSVal(..),
58 (Command(..), AsyncCommand(..), Result(..), JSContextRef(..), JSVal(..),
70 Object(..), JSValueReceived(..), JSM(..), Batch(..), JSValueForSend(..))
59 Object(..), JSValueReceived(..), JSM(..), Batch(..), JSValueForSend(..))
71import Language.Javascript.JSaddle.Exception (JSException(..))
60import Language.Javascript.JSaddle.Exception (JSException(..))
72import Language.Javascript.JSaddle.Native (wrapJSVal)
61import Language.Javascript.JSaddle.Native (wrapJSVal)
73−import Language.Javascript.JSaddle.WebSockets.Files (indexHtml, jsaddleJs)
74−import qualified Network.Wai as W
75− (responseLBS, requestMethod, Application, pathInfo)
76−import Data.Text (Text)
77−import qualified Network.HTTP.Types as H
78− (status403, status200, status405)
81−-- | Run the given 'JSM' action as the main entry point. Either directly
82−-- in GHCJS or as a Warp server on the given port on GHC.
83−run :: Int -> JSM () -> IO ()
88− runSettings (setPort port (setTimeout 3600 defaultSettings)) $
89− jsaddleOr defaultConnectionOptions (f >> syncPoint) jsaddleApp
92-- | Forces execution of pending asyncronous code
64-- | Forces execution of pending asyncronous code
@@ -154,58 +126,49 @@ sendAsyncCommand cmd = do
154 s <- doSendAsyncCommand <$> JSM ask
126 s <- doSendAsyncCommand <$> JSM ask
155 liftIO $ s cmd
127 liftIO $ s cmd
157−jsaddleOr :: ConnectionOptions -> JSM () -> Application -> Application
129+runJavaScript :: (Batch -> IO ()) -> JSM () -> IO (Result -> IO (), IO ())
158−jsaddleOr opts entryPoint = websocketsOr opts wsApp
130+runJavaScript sendBatch entryPoint = do
131+ startTime' <- getCurrentTime
132+ recvChan <- newTChanIO
133+ commandChan <- newTChanIO
134+ callbacks <- newTVarIO M.empty
135+ nextRef' <- newTVarIO 0
136+ let ctx = JSContextRef {
137+ startTime = startTime'
138+ , doSendCommand = \cmd -> do
139+ result <- newEmptyMVar
140+ atomically $ writeTChan commandChan (Right (cmd, result))
141+ takeMVar result >>= \case
142+ (ThrowJSValue (JSValueReceived v)) -> throwIO $ JSException (JSVal v)
144+ , doSendAsyncCommand = atomically . writeTChan commandChan . Left
145+ , addCallback = \(Object (JSVal val)) cb -> atomically $ modifyTVar callbacks (M.insert val cb)
146+ , freeCallback = \(Object (JSVal val)) -> atomically $ modifyTVar callbacks (M.delete val)
147+ , nextRef = nextRef'
149+ let processResult = \case
150+ (ProtocolError err) -> error $ "Protocol error : " <> T.unpack err
151+ (Callback f this a) -> do
152+ f'@(JSVal fNumber) <- runReaderT (unJSM $ wrapJSVal f) ctx
153+ this' <- runReaderT (unJSM $ wrapJSVal this) ctx
154+ args <- runReaderT (unJSM $ mapM wrapJSVal a) ctx
155+ (M.lookup fNumber <$> liftIO (readTVarIO callbacks)) >>= \case
156+ Nothing -> liftIO $ putStrLn "Callback called after it was freed"
157+ Just cb -> void . forkIO $ runReaderT (unJSM $ cb f' this' args) ctx
158+ m -> atomically $ writeTChan recvChan m
159+ _ <- forkIO . forever $ readBatch commandChan >>= \case
160+ (batch, Just resultMVar) -> do
162+ atomically (readTChan recvChan) >>= putMVar resultMVar
163+ (batch, Nothing) -> do
165+ atomically (readTChan recvChan) >>= \case
166+ SyncResult -> return ()
167+ ThrowJSValue e -> atomically (discardToSyncPoint commandChan) >>= (`putMVar` ThrowJSValue e)
168+ _ -> error "Unexpected result processing batch"
170+ return (processResult, runReaderT (unJSM entryPoint) ctx)
161− wsApp pending_conn = do
162− startTime <- getCurrentTime
163− conn <- acceptRequest pending_conn
164− recvChan <- newTChanIO
165− commandChan <- newTChanIO
166− callbacks <- newTVarIO M.empty
167− nextRef <- newTVarIO 0
168− let ctx = JSContextRef {
169− startTime = startTime
170− , doSendCommand = \cmd -> do
171− result <- newEmptyMVar
172− atomically $ writeTChan commandChan (Right (cmd, result))
173− takeMVar result >>= \case
174− (ThrowJSValue (JSValueReceived v)) -> throwIO $ JSException (JSVal v)
175− result -> return result
176− , doSendAsyncCommand = atomically . writeTChan commandChan . Left
177− , addCallback = \(Object (JSVal val)) cb -> atomically $ modifyTVar callbacks (M.insert val cb)
178− , freeCallback = \(Object (JSVal val)) -> atomically $ modifyTVar callbacks (M.delete val)
182− receiveDataMessage conn >>= \case
185− Nothing -> error $ "jsaddle WebSocket decode failed : " <> show t
186− Just (ProtocolError err) -> error $ "Protocol error : " <> T.unpack err
187− Just (Callback f this a) -> do
188− f'@(JSVal fNumber) <- runReaderT (unJSM $ wrapJSVal f) ctx
189− this' <- runReaderT (unJSM $ wrapJSVal this) ctx
190− args <- runReaderT (unJSM $ mapM wrapJSVal a) ctx
191− (M.lookup fNumber <$> liftIO (readTVarIO callbacks)) >>= \case
192− Nothing -> liftIO $ putStrLn "Callback called after it was freed"
193− Just cb -> void . forkIO $ runReaderT (unJSM $ cb f' this' args) ctx
194− Just m -> atomically $ writeTChan recvChan m
195− _ -> error "jsaddle WebSocket unexpected binary data"
196− forkIO . forever $ readBatch commandChan >>= \case
197− (batch, Just resultMVar) -> do
198− sendTextData conn $ encode batch
199− atomically (readTChan recvChan) >>= putMVar resultMVar
200− (batch, Nothing) -> do
201− sendTextData conn $ encode batch
202− atomically (readTChan recvChan) >>= \case
203− SyncResult -> return ()
204− ThrowJSValue e -> atomically (discardToSyncPoint commandChan) >>= (`putMVar` ThrowJSValue e)
205− _ -> error "Unexpected result processing batch"
207− runReaderT (unJSM entryPoint) ctx
209 readBatch :: TChan (Either AsyncCommand (Command, MVar Result)) -> IO (Batch, Maybe (MVar Result))
172 readBatch :: TChan (Either AsyncCommand (Command, MVar Result)) -> IO (Batch, Maybe (MVar Result))
210 readBatch chan = do
173 readBatch chan = do
211 first <- atomically $ readTChan chan -- We want at least one command to send
174 first <- atomically $ readTChan chan -- We want at least one command to send
@@ -230,19 +193,4 @@ jsaddleOr opts entryPoint = websocketsOr opts wsApp
230 readTChan chan >>= \case
193 readTChan chan >>= \case
231 Right (_, resultMVar) -> return resultMVar
194 Right (_, resultMVar) -> return resultMVar
232 _ -> discardToSyncPoint chan
195 _ -> discardToSyncPoint chan
234−jsaddleApp :: Application
235−jsaddleApp req = jsaddleAppPieces (W.pathInfo req) req
237−jsaddleAppPieces :: [Text] -> W.Application
238−jsaddleAppPieces _ req sendResponse
239− | notElem (W.requestMethod req) ["GET", "HEAD"] = sendResponse $ W.responseLBS
241− [("Content-Type", "text/plain")]
242− "Only GET or HEAD is supported"
243−jsaddleAppPieces [] req sendResponse = sendResponse $ W.responseLBS H.status200 [("Content-Type", "image/png")] indexHtml
244−jsaddleAppPieces ["jsaddle.js"] req sendResponse = sendResponse $ W.responseLBS H.status200 [("Content-Type", "image/png")] jsaddleJs
245−jsaddleAppPieces rawPieces req sendResponse = sendResponse $ W.responseLBS H.status403
246− [ ("Content-Type", "text/plain")