@@ -20,12 +20,9 @@ module Language.Javascript.JSaddle.WebSockets (
20 , jsaddleApp
20 , jsaddleApp
21 , jsaddleWithAppOr
21 , jsaddleWithAppOr
22 , jsaddleAppWithJs
22 , jsaddleAppWithJs
24 , jsaddleAppPartial
23 , jsaddleAppPartial
25 , jsaddleJs
24 , jsaddleJs
29 , debugWrapper
26 , debugWrapper
@@ -60,57 +57,45 @@ import qualified Data.Map as M (empty, insert, lookup)
60import Data.IORef
57import Data.IORef
61 (readIORef, newIORef, atomicModifyIORef')
58 (readIORef, newIORef, atomicModifyIORef')
62import Data.ByteString.Lazy (ByteString)
59import Data.ByteString.Lazy (ByteString)
63−import qualified Data.ByteString.Lazy as LBS
60+import qualified Data.ByteString.Lazy as LBS (stripPrefix)
64− (toStrict, stripPrefix)
65import Control.Concurrent.MVar
61import Control.Concurrent.MVar
66 (tryTakeMVar, MVar, tryPutMVar, modifyMVar_, putMVar, takeMVar,
62 (tryTakeMVar, MVar, tryPutMVar, modifyMVar_, putMVar, takeMVar,
67 readMVar, newMVar, newEmptyMVar, modifyMVar)
63 readMVar, newMVar, newEmptyMVar, modifyMVar)
68import Network.Wai.Handler.Warp
64import Network.Wai.Handler.Warp
69 (defaultSettings, setTimeout, setPort, runSettings)
65 (defaultSettings, setTimeout, setPort, runSettings)
70−import Foreign.Store (readStore, lookupStore, writeStore, Store(..))
66+import Foreign.Store (newStore, readStore, lookupStore)
71import Language.Javascript.JSaddle (askJSM)
67import Language.Javascript.JSaddle (askJSM)
72import Control.Monad.IO.Class (MonadIO(..))
68import Control.Monad.IO.Class (MonadIO(..))
74import Language.Javascript.JSaddle.WebSockets.Compat (getTextMessageByteString)
70import Language.Javascript.JSaddle.WebSockets.Compat (getTextMessageByteString)
75−import qualified Data.Text.Encoding as T (decodeUtf8)
77jsaddleOr :: ConnectionOptions -> JSM () -> Application -> IO Application
72jsaddleOr :: ConnectionOptions -> JSM () -> Application -> IO Application
78jsaddleOr opts entryPoint otherApp = do
73jsaddleOr opts entryPoint otherApp = do
79 syncHandlers <- newIORef M.empty
74 syncHandlers <- newIORef M.empty
80− asyncHandlers <- newIORef M.empty
81 let wsApp :: ServerApp
75 let wsApp :: ServerApp
82 wsApp pending_conn = do
76 wsApp pending_conn = do
83 conn <- acceptRequest pending_conn
77 conn <- acceptRequest pending_conn
84− initMsg <- receiveDataMessage conn
85− case getTextMessageByteString initMsg of
87− rec (processResult, processSyncResult, start) <- runJavaScript (sendTextData conn . encode) $ do
78+ rec (processResult, processSyncResult, start) <- runJavaScript (sendTextData conn . encode) $ do
88− syncKey <- T.pack . show . contextId <$> askJSM
79+ syncKey <- T.pack . show . contextId <$> askJSM
89− liftIO $ atomicModifyIORef' syncHandlers (\m -> (M.insert syncKey processSyncResult m, ()))
80+ liftIO $ atomicModifyIORef' syncHandlers (\m -> (M.insert syncKey processSyncResult m, ()))
90− liftIO $ atomicModifyIORef' asyncHandlers (\m -> (M.insert syncKey processResult m, ()))
91− liftIO $ sendTextData conn (encode syncKey)
81+ liftIO $ sendTextData conn (encode syncKey)
92− entryPoint
82+ entryPoint
96− M.lookup (T.decodeUtf8 $ LBS.toStrict syncKey) <$> readIORef syncHandlers >>= \case
97− Nothing -> error "jsaddle missing sync message handler"
98− Just processResult ->
99− forever $
83+ _ <- forkIO . forever $
100− receiveDataMessage conn >>= \msg -> case getTextMessageByteString msg of
84+ receiveDataMessage conn >>= \msg -> case getTextMessageByteString msg of
101− Just t ->
85+ Just t ->
102− case decode t of
86+ case decode t of
103− Nothing -> error $ "jsaddle Results decode failed : " <> show t
87+ Nothing -> error $ "jsaddle Results decode failed : " <> show t
104− Just r -> processResult r
88+ Just r -> processResult r
105− _ -> error "jsaddle WebSocket unexpected binary data"
89+ _ -> error "jsaddle WebSocket unexpected binary data"
106− _ -> error "jsaddle WebSocket unexpected binary data"
90+ start
108 -- Based on Network.WebSocket.forkPingThread
93 -- Based on Network.WebSocket.forkPingThread
109 waitTillClosed conn = ignore `handle` go 1
94 waitTillClosed conn = ignore `handle` go 1
111 go :: Int -> IO ()
96 go :: Int -> IO ()
112 go i = do
97 go i = do
113− threadDelay (10 * 1000 * 1000)
98+ threadDelay (1 * 1000 * 1000)
114 sendPing conn (T.pack $ show i)
99 sendPing conn (T.pack $ show i)
115 go (i + 1)
100 go (i + 1)
@@ -139,18 +124,13 @@ jsaddleOr opts entryPoint otherApp = do
141jsaddleApp :: Application
126jsaddleApp :: Application
142−jsaddleApp = jsaddleAppWithJs $ jsaddleJs False
127+jsaddleApp = jsaddleAppWithJs $ jsaddleJs Nothing False
144jsaddleAppWithJs :: ByteString -> Application
129jsaddleAppWithJs :: ByteString -> Application
145jsaddleAppWithJs js req sendResponse =
130jsaddleAppWithJs js req sendResponse =
146− jsaddleAppWithJsOr js
131+ fromMaybe
147− (\_ _ -> sendResponse $ W.responseLBS H.status403 [("Content-Type", "text/plain")] "Forbidden")
132+ (sendResponse $ W.responseLBS H.status403 [("Content-Type", "text/plain")] "Forbidden")
150−jsaddleAppWithJsOr :: ByteString -> Application -> Application
151−jsaddleAppWithJsOr js otherApp req sendResponse =
152− fromMaybe (otherApp req sendResponse)
153− (jsaddleAppPartialWithJs js req sendResponse)
133+ (jsaddleAppPartialWithJs js req sendResponse)
155jsaddleWithAppOr :: ConnectionOptions -> JSM () -> Application -> IO Application
135jsaddleWithAppOr :: ConnectionOptions -> JSM () -> Application -> IO Application
156jsaddleWithAppOr opts entryPoint otherApp = jsaddleOr opts entryPoint $ \req sendResponse ->
136jsaddleWithAppOr opts entryPoint otherApp = jsaddleOr opts entryPoint $ \req sendResponse ->
@@ -158,7 +138,7 @@ jsaddleWithAppOr opts entryPoint otherApp = jsaddleOr opts entryPoint $ \req sen
158 (jsaddleAppPartial req sendResponse))
138 (jsaddleAppPartial req sendResponse))
160jsaddleAppPartial :: Request -> (Response -> IO ResponseReceived) -> Maybe (IO ResponseReceived)
140jsaddleAppPartial :: Request -> (Response -> IO ResponseReceived) -> Maybe (IO ResponseReceived)
161−jsaddleAppPartial = jsaddleAppPartialWithJs $ jsaddleJs False
141+jsaddleAppPartial = jsaddleAppPartialWithJs $ jsaddleJs Nothing False
163indexResponse :: Response
143indexResponse :: Response
164indexResponse = W.responseLBS H.status200 [("Content-Type", "text/html")] indexHtml
144indexResponse = W.responseLBS H.status200 [("Content-Type", "text/html")] indexHtml
@@ -169,14 +149,11 @@ jsaddleAppPartialWithJs js req sendResponse = case (W.requestMethod req, W.pathI
169 ("GET", ["jsaddle.js"]) -> Just $ sendResponse $ W.responseLBS H.status200 [("Content-Type", "application/javascript")] js
149 ("GET", ["jsaddle.js"]) -> Just $ sendResponse $ W.responseLBS H.status200 [("Content-Type", "application/javascript")] js
170 _ -> Nothing
150 _ -> Nothing
172−jsaddleJs :: Bool -> ByteString
173−jsaddleJs = jsaddleJs' Nothing
175-- Use this to generate this string for embedding
152-- Use this to generate this string for embedding
176-- sed -e 's|\\|\\\\|g' -e 's|^| \\|' -e 's|$|\\n\\|' -e 's|"|\\"|g' data/jsaddle.js | pbcopy
153-- sed -e 's|\\|\\\\|g' -e 's|^| \\|' -e 's|$|\\n\\|' -e 's|"|\\"|g' data/jsaddle.js | pbcopy
177−jsaddleJs' :: Maybe ByteString -> Bool -> ByteString
154+jsaddleJs :: Maybe ByteString -> Bool -> ByteString
178−jsaddleJs' jsaddleUri refreshOnLoad = "\
155+jsaddleJs jsaddleUri refreshOnLoad = "\
179− \if(typeof global !== \"undefined\" && typeof require === \"function\") {\n\
156+ \if(typeof global !== \"undefined\") {\n\
180 \ global.window = global;\n\
157 \ global.window = global;\n\
181 \ global.WebSocket = require('ws');\n\
158 \ global.WebSocket = require('ws');\n\
@@ -188,30 +165,20 @@ jsaddleJs' jsaddleUri refreshOnLoad = "\
188 (jsaddleUri >>= LBS.stripPrefix "http")
165 (jsaddleUri >>= LBS.stripPrefix "http")
191− \ var ws0 = new WebSocket(wsaddress);\n\
168+ \ var ws = new WebSocket(wsaddress);\n\
192 \ var syncKey = \"\";\n\
169 \ var syncKey = \"\";\n\
194− \ ws0.onopen = function(e) {\n\
171+ \ ws.onopen = function(e) {\n\
195− \ ws0.send(\"\");\n\
196− \ var initialResults = [];\n\
197− \ var ws = {send: function(m) {initialResults.push(m);}};\n\
198 \ " <> initState <> "\n\
172 \ " <> initState <> "\n\
200− \ ws0.onmessage = function(e) {\n\
174+ \ ws.onmessage = function(e) {\n\
201 \ var batch = JSON.parse(e.data);\n\
175 \ var batch = JSON.parse(e.data);\n\
202 \ if(inCallback > 0) {\n\
176 \ if(inCallback > 0) {\n\
203 \ asyncBatch = batch;\n\
177 \ asyncBatch = batch;\n\
204 \ return;\n\
178 \ return;\n\
206 \ if(typeof batch === \"string\") {\n\
180 \ if(typeof batch === \"string\") {\n\
207− \ syncKey = batch;\n\
181+ \ syncKey = batch;\n" <>
208− \ var ws1 = new WebSocket(wsaddress);\n\
209− \ ws1.onopen = function(e) {\n\
210− \ ws1.send(syncKey);\n\
211− \ initialResults.forEach(function(m){ ws1.send(m); });\n\
212− \ initialResults = null;\n\
215 (if refreshOnLoad
182 (if refreshOnLoad
216 then " var xhr = new XMLHttpRequest();\n\
183 then " var xhr = new XMLHttpRequest();\n\
217 \ xhr.open('POST', '/reload/'+syncKey, true);\n\
184 \ xhr.open('POST', '/reload/'+syncKey, true);\n\
@@ -233,7 +200,7 @@ jsaddleJs' jsaddleUri refreshOnLoad = "\
233 \ return JSON.parse(xhr.response);})()")) <> "\
200 \ return JSON.parse(xhr.response);})()")) <> "\
236− \ ws0.onerror = function() {\n\
203+ \ ws.onerror = function() {\n\
237 \ setTimeout(connect, 1000);\n\
204 \ setTimeout(connect, 1000);\n\
@@ -249,14 +216,7 @@ debug :: Int -> JSM () -> IO ()
249debug port f = do
216debug port f = do
250 debugWrapper $ \withRefresh registerContext ->
217 debugWrapper $ \withRefresh registerContext ->
251 runSettings (setPort port (setTimeout 3600 defaultSettings)) =<<
218 runSettings (setPort port (setTimeout 3600 defaultSettings)) =<<
252− jsaddleOr defaultConnectionOptions (registerContext >> f >> syncPoint) (withRefresh $ jsaddleAppWithJs $ jsaddleJs True)
219+ jsaddleOr defaultConnectionOptions (registerContext >> f >> syncPoint) (withRefresh $ jsaddleAppWithJs $ jsaddleJs Nothing True)
253− putStrLn $ "<a href=\"http://localhost:" <> show port <> "\">run</a>"
255−debugOr :: Int -> JSM () -> Application -> IO ()
256−debugOr port f b = do
257− debugWrapper $ \withRefresh registerContext ->
258− runSettings (setPort port (setTimeout 3600 defaultSettings)) =<<
259− jsaddleOr defaultConnectionOptions (registerContext >> f >> syncPoint) (withRefresh $ jsaddleAppWithJsOr (jsaddleJs True) b)
260 putStrLn $ "<a href=\"http://localhost:" <> show port <> "\">run</a>"
220 putStrLn $ "<a href=\"http://localhost:" <> show port <> "\">run</a>"
262refreshMiddleware :: ((Response -> IO ResponseReceived) -> IO ResponseReceived) -> Middleware
222refreshMiddleware :: ((Response -> IO ResponseReceived) -> IO ResponseReceived) -> Middleware
@@ -307,13 +267,13 @@ debugWrapper run = do
307 start' <- takeMVar mvar
267 start' <- takeMVar mvar
308 n <- stop
268 n <- stop
309 start' n >>= restarter mvar
269 start' n >>= restarter mvar
310− lookupStore storeId >>= \case
270+ lookupStore shutdown_0 >>= \case
311 Nothing -> do
271 Nothing -> do
312 restartMVar <- newMVar start
272 restartMVar <- newMVar start
313 void . forkIO $ restarter restartMVar (return 0)
273 void . forkIO $ restarter restartMVar (return 0)
314− void $ writeStore (Store storeId) restartMVar
274+ void $ newStore restartMVar
315 Just shutdownStore -> do
275 Just shutdownStore -> do
316 restartMVar :: MVar (Int -> IO (IO Int)) <- readStore shutdownStore
276 restartMVar :: MVar (Int -> IO (IO Int)) <- readStore shutdownStore
317 void $ tryTakeMVar restartMVar
277 void $ tryTakeMVar restartMVar
318 putMVar restartMVar start
278 putMVar restartMVar start
319− where storeId = 354
279+ where shutdown_0 = 0