on master help sign in
Split jsaddle-warp out and add jsaddle-wkwebview
commitb8db013564004f94c0ae34009a6294999cd42f3a authorHamish Mackenzie <Hamish.K.Mackenzie@googlemail.com> dateFri 11 Nov 2016, 16:21 (10 years ago) parentdcf1bf4
25 files changed+554−433
jsaddle/README.md → README.md cabal.project+2 jsaddle-warp/LICENSE+21 jsaddle-warp/Setup.lhs+6 jsaddle-warp/jsaddle-warp.cabal+67 jsaddle/src/Language/Javascript/JSaddle/Test.hs → jsaddle-warp/src/Language/Javascript/JSaddle/Test.hs+1−1 jsaddle-warp/src/Language/Javascript/JSaddle/Warp.hs+43 jsaddle-warp/src/Language/Javascript/JSaddle/WebSockets.hs+72 jsaddle/tests/DocTest.hs → jsaddle-warp/tests/DocTest.hs+28−25 jsaddle-wkwebview/LICENSE+21 jsaddle-wkwebview/Setup.lhs+6 jsaddle-wkwebview/cbits/WKWebView.m+55 jsaddle-wkwebview/jsaddle-wkwebview.cabal+33 jsaddle-wkwebview/src/Language/Javascript/JSaddle/WKWebView.hs+77 jsaddle/data/index.html−9 jsaddle/data/jsaddle.js−201 jsaddle/jsaddle.cabal+5−38 jsaddle/src/Language/Javascript/JSaddle/Evaluate.hs+1−1 jsaddle/src/Language/Javascript/JSaddle/Monad.hs+2−3 jsaddle/src/Language/Javascript/JSaddle/Object.hs+6−6 jsaddle/src/Language/Javascript/JSaddle/Properties.hs+3−4 jsaddle/src/Language/Javascript/JSaddle/WebSockets.hs → jsaddle/src/Language/Javascript/JSaddle/Run.hs+47−99 jsaddle/src/Language/Javascript/JSaddle/WebSockets/Files.hs → jsaddle/src/Language/Javascript/JSaddle/Run/Files.hs+51−38 jsaddle/src/Language/Javascript/JSaddle/Value.hs+4−5 travis/build.sh+3−3
jsaddle/README.md → README.mdrenamed

The content is the same.

cabal.project+2
@@ -2,6 +2,8 @@ flags: -overloaded-methods -overloaded-signals -overloaded-properties
22
33packages:
44 jsaddle/
5+ jsaddle-warp/
6+ jsaddle-wkwebview/
57
68package gi-atk
79 flags: -overloaded-methods -overloaded-signals -overloaded-properties
jsaddle-warp/LICENSEnew file+21
@@ -0,0 +1,21 @@
1+Copyright (c) 2010-2012 Hamish Mackenzie, Victor Nazarov, Luite Stegeman
2+
3+Permission is hereby granted, free of charge, to any person obtaining
4+a copy of this software and associated documentation files (the
5+"Software"), to deal in the Software without restriction, including
6+without limitation the rights to use, copy, modify, merge, publish,
7+distribute, sublicense, and/or sell copies of the Software, and to
8+permit persons to whom the Software is furnished to do so, subject to
9+the following conditions:
10+
11+The above copyright notice and this permission notice shall be
12+included in all copies or substantial portions of the Software.
13+
14+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21+
jsaddle-warp/Setup.lhsnew file+6
@@ -0,0 +1,6 @@
1+#!/usr/bin/runhaskell
2+> module Main where
3+> import Distribution.Simple
4+> main :: IO ()
5+> main = defaultMain
6+
jsaddle-warp/jsaddle-warp.cabalnew file+67
@@ -0,0 +1,67 @@
1+name: jsaddle-warp
2+version: 0.6.0.0
3+cabal-version: >=1.10
4+build-type: Simple
5+license: MIT
6+license-file: LICENSE
7+maintainer: Hamish Mackenzie <Hamish.K.Mackenzie@googlemail.com>
8+synopsis: Interface for JavaScript that works with GHCJS and GHC
9+description:
10+ This package provides an EDSL for calling JavaScript that
11+ can be used both from GHCJS and GHC. When using GHC
12+ the application is run using Warp and WebSockets to
13+ drive a small JavaScipt helper.
14+category: Web, Javascript
15+author: Hamish Mackenzie
16+
17+source-repository head
18+ type: git
19+ location: https://github.com/ghcjs/jsaddle
20+
21+library
22+
23+ if !impl(ghcjs -any)
24+ exposed-modules:
25+ Language.Javascript.JSaddle.WebSockets
26+ build-depends:
27+ aeson >=0.8.0.2 && <1.1,
28+ containers >=0.5.6.2 && <0.6,
29+ http-types >=0.8.6 && <0.10,
30+ jsaddle >=0.6.0.0 && <0.7,
31+ stm >=2.4.4 && <2.5,
32+ text >=1.2.1.3 && <1.3,
33+ time >=1.5.0.1 && <1.7,
34+ transformers >=0.4.2.0 && <0.6,
35+ wai >=3.0.3.0 && <3.3,
36+ wai-websockets >=3.0.0.6 && <3.1,
37+ warp >=3.1.2 && <3.3,
38+ websockets >=0.9.5.0 && <0.10
39+ exposed-modules:
40+ Language.Javascript.JSaddle.Warp
41+ build-depends:
42+ base <5
43+ default-language: Haskell2010
44+ hs-source-dirs: src
45+ ghc-options: -ferror-spans -Wall
46+
47+test-suite test-tool
48+
49+ if !impl(ghcjs -any)
50+ build-depends:
51+ QuickCheck -any,
52+ bytestring -any,
53+ doctest >=0.10.1 && <0.12,
54+ filepath >=1.4.0.0 && <1.5,
55+ jsaddle -any,
56+ jsaddle-warp -any,
57+ lens -any,
58+ ref-tf -any,
59+ primitive -any,
60+ process >=1.2.3.0 && <1.5
61+ type: exitcode-stdio-1.0
62+ main-is: DocTest.hs
63+ build-depends:
64+ base -any
65+ default-language: Haskell2010
66+ hs-source-dirs: tests
67+ ghc-options: -ferror-spans -threaded
jsaddle/src/Language/Javascript/JSaddle/Test.hs → jsaddle-warp/src/Language/Javascript/JSaddle/Test.hsrenamed+1−1
@@ -31,7 +31,7 @@ import Control.Concurrent.MVar
3131import System.IO.Unsafe (unsafePerformIO)
3232import Control.Concurrent (threadDelay, forkIO, ThreadId)
3333import Data.Text (Text)
34−import Language.Javascript.JSaddle.WebSockets (run)
34+import Language.Javascript.JSaddle.Warp (run)
3535
3636context :: MVar JSContextRef
3737context = unsafePerformIO newEmptyMVar
jsaddle-warp/src/Language/Javascript/JSaddle/Warp.hsnew file+43
@@ -0,0 +1,43 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE LambdaCase #-}
3+{-# LANGUAGE OverloadedStrings #-}
4+-----------------------------------------------------------------------------
5+--
6+-- Module : Language.Javascript.JSaddle.WebSockets
7+-- Copyright : (c) Hamish Mackenzie
8+-- License : MIT
9+--
10+-- Maintainer : Hamish Mackenzie <Hamish.K.Mackenzie@googlemail.com>
11+--
12+-- |
13+--
14+-----------------------------------------------------------------------------
15+
16+module Language.Javascript.JSaddle.Warp (
17+ -- * Running JSM over WebSockets
18+ run
19+#ifndef ghcjs_HOST_OS
20+ , jsaddleOr
21+#endif
22+) where
23+
24+import Language.Javascript.JSaddle.Types (JSM)
25+#ifndef ghcjs_HOST_OS
26+import Network.Wai.Handler.Warp
27+ (defaultSettings, setTimeout, setPort, runSettings)
28+import Network.WebSockets (defaultConnectionOptions)
29+
30+import Language.Javascript.JSaddle.Run (syncPoint)
31+import Language.Javascript.JSaddle.WebSockets (jsaddleOr, jsaddleApp)
32+#endif
33+
34+-- | Run the given 'JSM' action as the main entry point. Either directly
35+-- in GHCJS or as a Warp server on the given port on GHC.
36+run :: Int -> JSM () -> IO ()
37+#ifdef ghcjs_HOST_OS
38+run _port = id
39+#else
40+run port f =
41+ runSettings (setPort port (setTimeout 3600 defaultSettings)) $
42+ jsaddleOr defaultConnectionOptions (f >> syncPoint) jsaddleApp
43+#endif
jsaddle-warp/src/Language/Javascript/JSaddle/WebSockets.hsnew file+72
@@ -0,0 +1,72 @@
1+{-# LANGUAGE LambdaCase #-}
2+{-# LANGUAGE OverloadedStrings #-}
3+-----------------------------------------------------------------------------
4+--
5+-- Module : Language.Javascript.JSaddle.WebSockets
6+-- Copyright : (c) Hamish Mackenzie
7+-- License : MIT
8+--
9+-- Maintainer : Hamish Mackenzie <Hamish.K.Mackenzie@googlemail.com>
10+--
11+-- |
12+--
13+-----------------------------------------------------------------------------
14+
15+module Language.Javascript.JSaddle.WebSockets (
16+ -- * Running JSM over WebSockets
17+ jsaddleOr
18+ , jsaddleApp
19+) where
20+
21+import Control.Monad (forever)
22+import Control.Concurrent (forkIO)
23+
24+import Data.Monoid ((<>))
25+import Data.Aeson (encode, decode)
26+
27+import Network.Wai (Application)
28+import Network.WebSockets
29+ (ConnectionOptions(..), sendTextData, receiveDataMessage,
30+ acceptRequest, ServerApp)
31+import qualified Network.WebSockets as WS (DataMessage(..))
32+import Network.Wai.Handler.WebSockets (websocketsOr)
33+
34+import Language.Javascript.JSaddle.Types (JSM(..))
35+import qualified Network.Wai as W
36+ (responseLBS, requestMethod, Application, pathInfo)
37+import Data.Text (Text)
38+import qualified Network.HTTP.Types as H
39+ (status403, status200, status405)
40+import Language.Javascript.JSaddle.Run (runJavaScript)
41+import Language.Javascript.JSaddle.Run.Files (indexHtml, jsaddleJs)
42+
43+jsaddleOr :: ConnectionOptions -> JSM () -> Application -> Application
44+jsaddleOr opts entryPoint = websocketsOr opts wsApp
45+ where
46+ wsApp :: ServerApp
47+ wsApp pending_conn = do
48+ conn <- acceptRequest pending_conn
49+ (processResult, start) <- runJavaScript (sendTextData conn . encode) entryPoint
50+ _ <- forkIO . forever $
51+ receiveDataMessage conn >>= \case
52+ (WS.Text t) ->
53+ case decode t of
54+ Nothing -> error $ "jsaddle WebSocket decode failed : " <> show t
55+ Just r -> processResult r
56+ _ -> error "jsaddle WebSocket unexpected binary data"
57+ start
58+
59+jsaddleApp :: Application
60+jsaddleApp req = jsaddleAppPieces (W.pathInfo req) req
61+
62+jsaddleAppPieces :: [Text] -> W.Application
63+jsaddleAppPieces _ req sendResponse
64+ | W.requestMethod req `notElem` ["GET", "HEAD"] = sendResponse $ W.responseLBS
65+ H.status405
66+ [("Content-Type", "text/plain")]
67+ "Only GET or HEAD is supported"
68+jsaddleAppPieces [] _req sendResponse = sendResponse $ W.responseLBS H.status200 [("Content-Type", "text/html")] indexHtml
69+jsaddleAppPieces ["jsaddle.js"] _req sendResponse = sendResponse $ W.responseLBS H.status200 [("Content-Type", "application/javascript")] jsaddleJs
70+jsaddleAppPieces _rawPieces _req sendResponse = sendResponse $ W.responseLBS H.status403
71+ [ ("Content-Type", "text/plain")
72+ ] "Forbidden"
jsaddle/tests/DocTest.hs → jsaddle-warp/tests/DocTest.hsrenamed+28−25
@@ -6,26 +6,27 @@ module Main (
66 main
77) where
88
9−#ifndef ghcjs_HOST_OS
109import Test.DocTest
1110import System.IO (stderr, BufferMode(..), stdout, hSetBuffering)
1211import System.FilePath ((</>))
13−import System.Exit (exitWith, ExitCode(..))
14−import System.Process (system)
12+import System.Exit (exitFailure, exitWith, ExitCode(..))
13+import System.Process (readProcess, system)
1514import Control.Concurrent (forkIO)
1615import Control.Monad (void)
1716import Data.Monoid ((<>))
18−import Paths_jsaddle (getDataDir)
19−#endif
17+import System.Environment (getArgs)
18+import Language.Javascript.JSaddle.Run.Files (jsaddleJs)
19+import qualified Data.ByteString.Lazy.Char8 as BS (unpack)
2020
2121main :: IO ()
2222main = do
23−#ifdef ghcjs_HOST_OS
24− putStrLn "TODO find a way to run doctest tests with GHCJS"
25−#else
23+ jsaddlePath <- getArgs >>= \case
24+ [arg] -> return arg
25+ _ -> do
26+ putStrLn "Please give the path to the jsaddle package source"
27+ exitFailure
2628 hSetBuffering stdout LineBuffering
2729 hSetBuffering stderr LineBuffering
28− dataDir <- getDataDir
2930 node <- system "nodejs --version" >>= \case
3031 ExitSuccess -> return "nodejs"
3132 _ -> return "node"
@@ -34,10 +35,9 @@ main = do
3435 e -> do
3536 putStrLn "Node.js not found"
3637 exitWith e
37− forkIO . void . system $ node <> " --harmony " <> dataDir </> "data/jsaddle.js"
38+ forkIO . void $ readProcess node ["--harmony"] (BS.unpack jsaddleJs) >>= putStr
3839 doctest [
3940 "-hide-all-packages",
40− "-package=template-haskell-" ++ VERSION_template_haskell,
4141 "-package=base-" ++ VERSION_base,
4242 "-package=lens-" ++ VERSION_lens,
4343 "-package=text-" ++ VERSION_text,
@@ -57,17 +57,20 @@ main = do
5757 "-package=process-" ++ VERSION_process,
5858 "-package=filepath-" ++ VERSION_filepath,
5959 "-package=ref-tf-" ++ VERSION_ref_tf,
60− "-isrc",
61− "src/Language/Javascript/JSaddle/Arguments.hs",
62− "src/Language/Javascript/JSaddle/Classes.hs",
63− "src/Language/Javascript/JSaddle/Evaluate.hs",
64− "src/Language/Javascript/JSaddle/Exception.hs",
65− "src/Language/Javascript/JSaddle/Monad.hs",
66− "src/Language/Javascript/JSaddle/Native.hs",
67− "src/Language/Javascript/JSaddle/Object.hs",
68− "src/Language/Javascript/JSaddle/Properties.hs",
69− "src/Language/Javascript/JSaddle/String.hs",
70− "src/Language/Javascript/JSaddle/Test.hs",
71− "src/Language/Javascript/JSaddle/Types.hs",
72− "src/Language/Javascript/JSaddle/Value.hs" ]
73−#endif
60+ "-i" <> jsaddlePath </> "jsaddle-warp/src",
61+ jsaddlePath </> "jsaddle-warp/src/Language/Javascript/JSaddle/Test.hs",
62+ "-i" <> jsaddlePath </> "jsaddle",
63+ jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle.hs",
64+ jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Arguments.hs",
65+ jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Classes.hs",
66+ jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Evaluate.hs",
67+ jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Exception.hs",
68+ jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Monad.hs",
69+ jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Native.hs",
70+ jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Object.hs",
71+ jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Properties.hs",
72+ jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Run.hs",
73+ jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Run/Files.hs",
74+ jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/String.hs",
75+ jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Types.hs",
76+ jsaddlePath </> "jsaddle/src/Language/Javascript/JSaddle/Value.hs" ]
jsaddle-wkwebview/LICENSEnew file+21
@@ -0,0 +1,21 @@
1+Copyright (c) 2010-2012 Hamish Mackenzie, Victor Nazarov, Luite Stegeman
2+
3+Permission is hereby granted, free of charge, to any person obtaining
4+a copy of this software and associated documentation files (the
5+"Software"), to deal in the Software without restriction, including
6+without limitation the rights to use, copy, modify, merge, publish,
7+distribute, sublicense, and/or sell copies of the Software, and to
8+permit persons to whom the Software is furnished to do so, subject to
9+the following conditions:
10+
11+The above copyright notice and this permission notice shall be
12+included in all copies or substantial portions of the Software.
13+
14+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21+
jsaddle-wkwebview/Setup.lhsnew file+6
@@ -0,0 +1,6 @@
1+#!/usr/bin/runhaskell
2+> module Main where
3+> import Distribution.Simple
4+> main :: IO ()
5+> main = defaultMain
6+
jsaddle-wkwebview/cbits/WKWebView.mnew file+55
@@ -0,0 +1,55 @@
1+#include "HsFFI.h"
2+#import <Foundation/Foundation.h>
3+#import <WebKit/WebKit.h>
4+
5+extern void jsaddleStart(HsStablePtr);
6+extern void jsaddleResult(HsStablePtr, const char * _Nonnull result);
7+
8+@interface JSaddleHandler : NSObject<WKNavigationDelegate, WKScriptMessageHandler>
9+
10+@property (nonatomic, assign) HsStablePtr startHandler;
11+@property (nonatomic, assign) HsStablePtr resultHandler;
12+
13+- (instancetype)initHandler:(HsStablePtr)startHandler resultHandler:(HsStablePtr)resultHandler;
14+
15+@end
16+
17+@implementation JSaddleHandler
18+
19+- (instancetype)initHandler:(HsStablePtr)startHandler resultHandler:(HsStablePtr)resultHandler
20+{
21+ self = [super init];
22+ if (self) {
23+ _startHandler = startHandler;
24+ _resultHandler = resultHandler;
25+ }
26+ return self;
27+}
28+
29+- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
30+{
31+ jsaddleStart(self.startHandler);
32+}
33+
34+- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
35+{
36+ NSString * s = (NSString *) message.body;
37+ jsaddleResult(self.resultHandler, [s UTF8String]);
38+}
39+
40+@end
41+
42+void addJSaddleHandler(WKWebView *webView, HsStablePtr startHandler, HsStablePtr resultHandler) {
43+ JSaddleHandler * handler = [[JSaddleHandler alloc] initHandler:startHandler resultHandler:resultHandler];
44+ [[[webView configuration] userContentController] addScriptMessageHandler:handler name:@"jsaddle"];
45+ webView.navigationDelegate = handler;
46+}
47+
48+void evaluateJavaScript(WKWebView *webView, const char * _Nonnull js) {
49+ [webView evaluateJavaScript:[NSString stringWithCString:js encoding:NSUTF8StringEncoding] completionHandler:NULL];
50+}
51+
52+void loadHTMLString(WKWebView *webView, const char * _Nonnull html) {
53+ [webView loadHTMLString:[NSString stringWithCString:html encoding:NSUTF8StringEncoding] baseURL:NULL];
54+}
55+
jsaddle-wkwebview/jsaddle-wkwebview.cabalnew file+33
@@ -0,0 +1,33 @@
1+name: jsaddle-wkwebview
2+version: 0.6.0.0
3+cabal-version: >=1.10
4+build-type: Simple
5+license: MIT
6+license-file: LICENSE
7+maintainer: Hamish Mackenzie <Hamish.K.Mackenzie@googlemail.com>
8+synopsis: Interface for JavaScript that works with GHCJS and GHC
9+description:
10+ This package provides an EDSL for calling JavaScript that
11+ can be used both from GHCJS and GHC. When using GHC
12+ the application is run using Warp and WebSockets to
13+ drive a small JavaScipt helper.
14+category: Web, Javascript
15+author: Hamish Mackenzie
16+
17+source-repository head
18+ type: git
19+ location: https://github.com/ghcjs/jsaddle
20+
21+library
22+ exposed-modules:
23+ Language.Javascript.JSaddle.WKWebView
24+ build-depends:
25+ aeson >=0.8.0.2 && <1.1,
26+ base <5,
27+ bytestring >=0.10.6.0 && <0.11,
28+ jsaddle >= 0.6.0.0 && <0.7
29+ default-language: Haskell2010
30+ hs-source-dirs: src
31+ ghc-options: -ferror-spans -Wall
32+ c-sources: cbits/WKWebView.m
33+
jsaddle-wkwebview/src/Language/Javascript/JSaddle/WKWebView.hsnew file+77
@@ -0,0 +1,77 @@
1+{-# LANGUAGE ForeignFunctionInterface, OverloadedStrings #-}
2+module Language.Javascript.JSaddle.WKWebView
3+ ( jsaddleMain
4+ , WKWebView(..)
5+ ) where
6+
7+import Control.Monad (void, join)
8+import Control.Concurrent (forkIO)
9+
10+import Data.Monoid ((<>))
11+import Data.ByteString (useAsCString, packCString)
12+import Data.ByteString.Lazy (ByteString, toStrict, fromStrict)
13+import Data.Aeson (encode, decode)
14+
15+import Foreign.C.String (CString)
16+import Foreign.Ptr (Ptr)
17+import Foreign.StablePtr (StablePtr, newStablePtr, deRefStablePtr)
18+
19+import Language.Javascript.JSaddle (Result, JSM)
20+import Language.Javascript.JSaddle.Run (runJavaScript)
21+import Language.Javascript.JSaddle.Run.Files (initState, runBatch)
22+
23+newtype WKWebView = WKWebView (Ptr WKWebView)
24+
25+foreign export ccall jsaddleStart :: StablePtr (IO ()) -> IO ()
26+foreign export ccall jsaddleResult :: StablePtr (Result -> IO ()) -> CString -> IO ()
27+foreign import ccall addJSaddleHandler :: WKWebView -> StablePtr (IO ()) -> StablePtr (Result -> IO ()) -> IO ()
28+foreign import ccall loadHTMLString :: WKWebView -> CString -> IO ()
29+foreign import ccall evaluateJavaScript :: WKWebView -> CString -> IO ()
30+
31+jsaddleMain :: JSM () -> WKWebView -> IO ()
32+jsaddleMain f webView = do
33+ (processResult, start) <- runJavaScript (\batch ->
34+ useAsCString (toStrict $ "runJSaddleBatch(" <> encode batch <> ");") $
35+ evaluateJavaScript webView)
36+ f
37+
38+ startHandler <- newStablePtr (void $ forkIO start)
39+ resultHandler <- newStablePtr processResult
40+ addJSaddleHandler webView startHandler resultHandler
41+
42+ useAsCString (toStrict indexHtml) $ loadHTMLString webView
43+ useAsCString (toStrict jsaddleJs) $ evaluateJavaScript webView
44+
45+jsaddleStart :: StablePtr (IO ()) -> IO ()
46+jsaddleStart ptrHandler = join (deRefStablePtr ptrHandler)
47+
48+jsaddleResult :: StablePtr (Result -> IO ()) -> CString -> IO ()
49+jsaddleResult ptrHandler s = do
50+ processResult <- deRefStablePtr ptrHandler
51+ result <- packCString s
52+ case decode (fromStrict result) of
53+ Nothing -> error $ "jsaddle WebSocket decode failed : " <> show result
54+ Just r -> processResult r
55+
56+jsaddleJs :: ByteString
57+jsaddleJs = "\
58+ \runJSaddleBatch = (function() {\n\
59+ \ " <> initState <> "\n\
60+ \ return function(batch) {\n\
61+ \ " <> runBatch (\a -> "window.webkit.messageHandlers.jsaddle.postMessage(JSON.stringify(" <> a <> "));") <> "\
62+ \ };\n\
63+ \})();\n\
64+ \"
65+
66+indexHtml :: ByteString
67+indexHtml =
68+ "<!DOCTYPE html>\n\
69+ \<html>\n\
70+ \<head>\n\
71+ \<title>JSaddle</title>\n\
72+ \</head>\n\
73+ \<body>\n\
74+ \</body>\n\
75+ \</html>"
76+
77+-- jsaddle
jsaddle/data/index.htmldeleted−9
@@ -1,9 +0,0 @@
1−<!DOCTYPE html>
2−<html>
3−<head>
4−<title>JSaddle</title>
5−</head>
6−<body>
7−</body>
8−<script src="jsaddle.js"></script>
9−</html>
jsaddle/data/jsaddle.jsdeleted−201
@@ -1,201 +0,0 @@
1−if(typeof global !== "undefined") {
2− global.window = global;
3− global.WebSocket = require('ws');
4−}
5−
6−var connect = function() {
7− var wsaddress =
8− typeof window.location === "undefined"
9− ? "ws://localhost:3709/"
10− : window.location.protocol.replace('http', 'ws')+"//"+window.location.hostname+(window.location.port?(":"+window.location.port):"");
11−
12− var ws = new WebSocket(wsaddress);
13−
14− ws.onopen = function(e) {
15− var jsaddle_values = new Map();
16− jsaddle_values.set(0, null);
17− jsaddle_values.set(1, undefined);
18− jsaddle_values.set(2, false);
19− jsaddle_values.set(3, true);
20− jsaddle_values.set(4, window);
21− var jsaddle_index = 100;
22−
23− ws.onmessage = function(e) {
24− var batch = JSON.parse(e.data);
25−
26− var processBatch = function(timestamp) {
27− try {
28− var nAsyncLength = batch[0].length;
29− for (var nAsync = 0; nAsync != nAsyncLength; nAsync++) {
30− var d = batch[0][nAsync];
31− switch (d.tag) {
32− case "FreeRef":
33− jsaddle_values.delete(d.contents);
34− break;
35− case "SetPropertyByName":
36− jsaddle_values.get(d.contents[0])[d.contents[1]]=jsaddle_values.get(d.contents[2]);
37− break;
38− case "SetPropertyAtIndex":
39− jsaddle_values.get(d.contents[0])[d.contents[1]]=jsaddle_values.get(d.contents[2]);
40− break;
41− case "EvaluateScript":
42− var n = d.contents[1];
43− jsaddle_values.set(n, eval(d.contents[0]));
44− break;
45− case "StringToValue":
46− var n = d.contents[1];
47− jsaddle_values.set(n, d.contents[0]);
48− break;
49− case "GetPropertyByName":
50− var n = d.contents[2];
51− jsaddle_values.set(n, jsaddle_values.get(d.contents[0])[d.contents[1]]);
52− break;
53− case "GetPropertyAtIndex":
54− var n = d.contents[2];
55− jsaddle_values.set(n, jsaddle_values.get(d.contents[0])[d.contents[1]]);
56− break;
57− case "NumberToValue":
58− var n = d.contents[1];
59− jsaddle_values.set(n, d.contents[0]);
60− break;
61− case "NewEmptyObject":
62− var n = d.contents;
63− jsaddle_values.set(n, {});
64− break;
65− case "NewCallback":
66− (function() {
67− var nFunction = d.contents;
68− jsaddle_values.set(nFunction, function() {
69− var nThis = ++jsaddle_index;
70− jsaddle_values.set(nThis, this);
71− var args = [];
72− for (var i = 0; i != arguments.length; i++) {
73− var nArg = ++jsaddle_index;
74− jsaddle_values.set(nArg, arguments[i]);
75− args[i] = nArg;
76− }
77− ws.send(JSON.stringify({"tag": "Callback", "contents": [nFunction, nThis, args]}));
78− })})();
79− break;
80− case "CallAsFunction":
81− var n = d.contents[3];
82− jsaddle_values.set(n,
83− jsaddle_values.get(d.contents[0]).apply(jsaddle_values.get(d.contents[1]),
84− d.contents[2].map(function(arg){return jsaddle_values.get(arg);})));
85− break;
86− case "CallAsConstructor":
87− var n = d.contents[2];
88− var r;
89− var f = jsaddle_values.get(d.contents[0]);
90− var a = d.contents[1].map(function(arg){return jsaddle_values.get(arg);});
91− switch(a.length) {
92− case 0 : r = new f(); break;
93− case 1 : r = new f(a[0]); break;
94− case 2 : r = new f(a[0],a[1]); break;
95− case 3 : r = new f(a[0],a[1],a[2]); break;
96− case 4 : r = new f(a[0],a[1],a[2],a[3]); break;
97− case 5 : r = new f(a[0],a[1],a[2],a[3],a[4]); break;
98− case 6 : r = new f(a[0],a[1],a[2],a[3],a[4],a[5]); break;
99− case 7 : r = new f(a[0],a[1],a[2],a[3],a[4],a[5],a[6]); break;
100− default:
101− var ret;
102− var temp = function() {
103− ret = f.apply(this, a);
104− };
105− temp.prototype = f.prototype;
106− var i = new temp();
107− if(ret instanceof Object)
108− r = ret;
109− else {
110− i.constructor = f;
111− r = i;
112− }
113− }
114− jsaddle_values.set(n, r);
115− break;
116− case "NewArray":
117− var n = d.contents[1];
118− jsaddle_values.set(n, d.contents[0].map(function(v){return jsaddle_values.get(v);}));
119− break;
120− case "SyncWithAnimationFrame":
121− var n = d.contents[0];
122− jsaddle_values.set(n, timestamp);
123− break;
124− default:
125− ws.send(JSON.stringify({"tag": "ProtocolError", "contents": e.data}));
126− return;
127− }
128− }
129− var d = batch[1];
130− switch (d.tag) {
131− case "ValueToString":
132− var val = jsaddle_values.get(d.contents);
133− var s = val === null ? "null" : val === undefined ? "undefined" : val.toString();
134− ws.send(JSON.stringify({"tag": "ValueToStringResult", "contents": s}));
135− break;
136− case "ValueToBool":
137− ws.send(JSON.stringify({"tag": "ValueToBoolResult", "contents": jsaddle_values.get(d.contents) ? true : false}));
138− break;
139− case "ValueToNumber":
140− ws.send(JSON.stringify({"tag": "ValueToNumberResult", "contents": Number(jsaddle_values.get(d.contents))}));
141− break;
142− case "ValueToJSON":
143− var s = jsaddle_values.get(d.contents) === undefined ? "" : JSON.stringify(jsaddle_values.get(d.contents));
144− ws.send(JSON.stringify({"tag": "ValueToJSONResult", "contents": s}));
145− break;
146− case "DeRefVal":
147− var n = d.contents;
148− var v = jsaddle_values.get(n);
149− var c = (v === null ) ? [0, ""] :
150− (v === undefined ) ? [1, ""] :
151− (v === false ) ? [2, ""] :
152− (v === true ) ? [3, ""] :
153− (typeof v === "number") ? [-1, v.toString()] :
154− (typeof v === "string") ? [-2, v]
155− : [n, ""];
156− ws.send(JSON.stringify({"tag": "DeRefValResult", "contents": c}));
157− break;
158− case "IsNull":
159− ws.send(JSON.stringify({"tag": "IsNullResult", "contents": jsaddle_values.get(d.contents) === null}));
160− break;
161− case "IsUndefined":
162− ws.send(JSON.stringify({"tag": "IsUndefinedResult", "contents": jsaddle_values.get(d.contents) === undefined}));
163− break;
164− case "InstanceOf":
165− ws.send(JSON.stringify({"tag": "InstanceOfResult", "contents": jsaddle_values.get(d.contents[0]) instanceof jsaddle_values.get(d.contents[1])}));
166− break;
167− case "StrictEqual":
168− ws.send(JSON.stringify({"tag": "StrictEqualResult", "contents": jsaddle_values.get(d.contents[0]) === jsaddle_values.get(d.contents[1])}));
169− break;
170− case "PropertyNames":
171− var result = [];
172− for (name in jsaddle_values.get(d.contents)) { result.push(name); }
173− ws.send(JSON.stringify({"tag": "PropertyNamesResult", "contents": result}));
174− break;
175− case "Sync":
176− ws.send(JSON.stringify({"tag": "SyncResult", "contents": []}));
177− break;
178− default:
179− ws.send(JSON.stringify({"tag": "ProtocolError", "contents": e.data}));
180− }
181− }
182− catch (err) {
183− var n = ++jsaddle_index;
184− jsaddle_values.set(n, err);
185− ws.send(JSON.stringify({"tag": "ThrowJSValue", "contents": n}));
186− }
187− };
188− if(batch[2]) {
189− window.requestAnimationFrame(processBatch);
190− }
191− else {
192− processBatch(window.performance ? window.performance.now() : null);
193− }
194− };
195− };
196− ws.onerror = function() {
197− setTimeout(connect, 1000);
198− };
199−}
200−
201−connect();
jsaddle/jsaddle.cabal+5−38
@@ -1,5 +1,5 @@
11name: jsaddle
2−version: 0.5.2.0
2+version: 0.6.0.0
33cabal-version: >=1.10
44build-type: Simple
55license: MIT
@@ -13,9 +13,6 @@ description:
1313 drive a small JavaScipt helper.
1414category: Web, Javascript
1515author: Hamish Mackenzie
16−data-files:
17− data/index.html
18− data/jsaddle.js
1916
2017source-repository head
2118 type: git
@@ -28,10 +25,6 @@ library
2825 ghcjs-base -any,
2926 ghcjs-prim -any
3027 else
31− exposed-modules:
32− Language.Javascript.JSaddle.WebSockets
33− Language.Javascript.JSaddle.WebSockets.Files
34− Language.Javascript.JSaddle.Test
3528 build-depends:
3629 aeson >=0.8.0.2 && <1.1,
3730 bytestring >=0.10.6.0 && <0.11,
@@ -41,11 +34,7 @@ library
4134 process >=1.2.3.0 && <1.5,
4235 ref-tf >=0.4.0.1 && <0.5,
4336 stm >=2.4.4 && <2.5,
44− time >=1.5.0.1 && <1.7,
45− wai >=3.0.3.0 && <3.3,
46− wai-websockets >=3.0.0.6 && <3.1,
47− warp >=3.1.2 && <3.3,
48− websockets >=0.9.5.0 && <0.10
37+ time >=1.5.0.1 && <1.7
4938 exposed-modules:
5039 Language.Javascript.JSaddle
5140 Language.Javascript.JSaddle.Arguments
@@ -56,10 +45,11 @@ library
5645 Language.Javascript.JSaddle.Native
5746 Language.Javascript.JSaddle.Object
5847 Language.Javascript.JSaddle.Properties
48+ Language.Javascript.JSaddle.Run
49+ Language.Javascript.JSaddle.Run.Files
5950 Language.Javascript.JSaddle.String
6051 Language.Javascript.JSaddle.Value
6152 Language.Javascript.JSaddle.Types
62− Paths_jsaddle
6353 build-depends:
6454 base <5,
6555 lens >=3.8.5 && <4.16,
@@ -69,29 +59,6 @@ library
6959 transformers >=0.4.2.0 && <0.6
7060 default-language: Haskell2010
7161 hs-source-dirs: src
72− ghc-options: -ferror-spans
62+ ghc-options: -ferror-spans -Wall
7363
74−test-suite test-tool
7564
76− if !impl(ghcjs -any)
77− build-depends:
78− QuickCheck -any,
79− bytestring >=0.10.6.0 && <0.11,
80− doctest >=0.10.1 && <0.12,
81− filepath >=1.4.0.0 && <1.5,
82− http-types >=0.8.6 && <0.10,
83− jsaddle -any,
84− process >=1.2.3.0 && <1.5,
85− text >=1.2.1.3 && <1.3,
86− vector >=0.10.12.3 && <0.12,
87− wai >=3.0.3.0 && <3.3,
88− wai-websockets >=3.0.0.6 && <3.1,
89− warp >=3.1.2 && <3.3,
90− websockets >=0.9.5.0 && <0.10
91− type: exitcode-stdio-1.0
92− main-is: DocTest.hs
93− build-depends:
94− base -any
95− default-language: Haskell2010
96− hs-source-dirs: tests
97− ghc-options: -ferror-spans -threaded
jsaddle/src/Language/Javascript/JSaddle/Evaluate.hs+1−1
@@ -29,7 +29,7 @@ import Language.Javascript.JSaddle.Types (JSString)
2929#else
3030import Language.Javascript.JSaddle.Native
3131 (withJSString)
32−import Language.Javascript.JSaddle.WebSockets
32+import Language.Javascript.JSaddle.Run
3333 (AsyncCommand(..), sendLazyCommand)
3434#endif
3535import Language.Javascript.JSaddle.Classes
jsaddle/src/Language/Javascript/JSaddle/Monad.hs+2−3
@@ -21,7 +21,6 @@ module Language.Javascript.JSaddle.Monad (
2121
2222 -- * Running JavaScript in a JavaScript context
2323 , askJSM
24− , run
2524 , runJSM
2625 , runJSaddle
2726
@@ -36,12 +35,12 @@ module Language.Javascript.JSaddle.Monad (
3635 , bracket
3736) where
3837
39−import Prelude hiding (catch, read)
38+import Prelude hiding (read)
4039import Control.Monad.Trans.Reader (runReaderT, ask, ReaderT(..))
4140import Control.Monad.IO.Class (MonadIO(..))
4241import qualified Control.Exception as E (Exception, catch, bracket)
4342import Language.Javascript.JSaddle.Types (JSM(..), MonadJSM, liftJSM, JSContextRef(..))
44−import Language.Javascript.JSaddle.WebSockets (run, syncPoint, syncAfter, waitForAnimationFrame, nextAnimationFrame)
43+import Language.Javascript.JSaddle.Run (syncPoint, syncAfter, waitForAnimationFrame, nextAnimationFrame)
4544
4645-- | Gets the JavaScript context from the monad
4746askJSM :: MonadJSM m => m JSContextRef
jsaddle/src/Language/Javascript/JSaddle/Object.hs+6−6
@@ -109,7 +109,7 @@ import Data.Coerce (coerce)
109109#else
110110import Language.Javascript.JSaddle.Native
111111 (wrapJSString, withJSVals, withObject)
112−import Language.Javascript.JSaddle.WebSockets
112+import Language.Javascript.JSaddle.Run
113113 (Command(..), AsyncCommand(..), Result(..), sendCommand, sendLazyCommand)
114114#endif
115115import Language.Javascript.JSaddle.Value (valToObject)
@@ -443,10 +443,10 @@ foreign import javascript unsafe "$r = function () { $1(this, arguments); }"
443443 makeFunctionWithCallback :: Callback (JSVal -> JSVal -> IO ()) -> IO Object
444444#else
445445function f = do
446− obj <- Object <$> sendLazyCommand NewCallback
446+ object <- Object <$> sendLazyCommand NewCallback
447447 add <- addCallback <$> askJSM
448− liftIO $ add obj f
449− return $ Function obj
448+ liftIO $ add object f
449+ return $ Function object
450450#endif
451451
452452freeFunction :: Function -> JSM ()
@@ -454,9 +454,9 @@ freeFunction :: Function -> JSM ()
454454freeFunction (Function callback _) = liftIO $
455455 releaseCallback callback
456456#else
457−freeFunction (Function obj) = do
457+freeFunction (Function object) = do
458458 free <- freeCallback <$> askJSM
459− liftIO $ free obj
459+ liftIO $ free object
460460#endif
461461
462462instance ToJSVal Function where
jsaddle/src/Language/Javascript/JSaddle/Properties.hs+3−4
@@ -31,14 +31,14 @@ module Language.Javascript.JSaddle.Properties (
3131
3232import Language.Javascript.JSaddle.Classes
3333 (ToJSVal(..), ToJSString(..))
34−import Language.Javascript.JSaddle.Monad (askJSM, JSM)
34+import Language.Javascript.JSaddle.Monad (JSM)
3535import Language.Javascript.JSaddle.Types (Object(..))
3636#ifdef ghcjs_HOST_OS
3737import Language.Javascript.JSaddle.Types (JSString)
3838#else
3939import Language.Javascript.JSaddle.Native
4040 (withObject, withJSString, withToJSVal)
41−import Language.Javascript.JSaddle.WebSockets
41+import Language.Javascript.JSaddle.Run
4242 (AsyncCommand(..), sendLazyCommand, sendAsyncCommand)
4343#endif
4444import Language.Javascript.JSaddle.Value (JSVal)
@@ -108,8 +108,7 @@ objSetPropertyAtIndex this index val = do
108108foreign import javascript unsafe "$2[$1]=$3"
109109 js_trySetAtIndex :: Int -> Object -> JSVal -> IO ()
110110#else
111−objSetPropertyAtIndex this index val = do
112− gctxt <- askJSM
111+objSetPropertyAtIndex this index val =
113112 withObject this $ \rthis ->
114113 withToJSVal val $ \rval ->
115114 sendAsyncCommand $ SetPropertyAtIndex rthis index rval
jsaddle/src/Language/Javascript/JSaddle/WebSockets.hs → jsaddle/src/Language/Javascript/JSaddle/Run.hsrenamed+47−99
@@ -3,7 +3,7 @@
33{-# LANGUAGE OverloadedStrings #-}
44-----------------------------------------------------------------------------
55--
6−-- Module : Language.Javascript.JSaddle.WebSockets
6+-- Module : Language.Javascript.JSaddle.Run
77-- Copyright : (c) Hamish Mackenzie
88-- License : MIT
99--
@@ -13,16 +13,15 @@
1313--
1414-----------------------------------------------------------------------------
1515
16−module Language.Javascript.JSaddle.WebSockets (
17− -- * Running JSM over WebSockets
18− run
16+module Language.Javascript.JSaddle.Run (
17+ -- * Running JSM
18+ runJavaScript
1919 , syncPoint
2020 , syncAfter
2121 , waitForAnimationFrame
2222 , nextAnimationFrame
2323#ifndef ghcjs_HOST_OS
24− , jsaddleOr
25− -- * Functions used to implement JSaddle over WebSockets
24+ -- * Functions used to implement JSaddle using JSON messaging
2625 , AsyncCommand(..)
2726 , Command(..)
2827 , Result(..)
@@ -53,40 +52,13 @@ import Control.Concurrent.MVar
5352import Data.Monoid ((<>))
5453import qualified Data.Text as T (unpack)
5554import qualified Data.Map as M (lookup, delete, insert, empty)
56−import Data.Aeson (encode, decode)
5755import Data.Time.Clock (getCurrentTime,diffUTCTime)
5856
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)
67−
6857import Language.Javascript.JSaddle.Types
6958 (Command(..), AsyncCommand(..), Result(..), JSContextRef(..), JSVal(..),
7059 Object(..), JSValueReceived(..), JSM(..), Batch(..), JSValueForSend(..))
7160import Language.Javascript.JSaddle.Exception (JSException(..))
7261import 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)
79−#endif
80−
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 ()
84−#ifdef ghcjs_HOST_OS
85−run _port = id
86−#else
87−run port f =
88− runSettings (setPort port (setTimeout 3600 defaultSettings)) $
89− jsaddleOr defaultConnectionOptions (f >> syncPoint) jsaddleApp
9062#endif
9163
9264-- | Forces execution of pending asyncronous code
@@ -154,58 +126,49 @@ sendAsyncCommand cmd = do
154126 s <- doSendAsyncCommand <$> JSM ask
155127 liftIO $ s cmd
156128
157−jsaddleOr :: ConnectionOptions -> JSM () -> Application -> Application
158−jsaddleOr opts entryPoint = websocketsOr opts wsApp
129+runJavaScript :: (Batch -> IO ()) -> JSM () -> IO (Result -> IO (), IO ())
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)
143+ r -> return r
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'
148+ }
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
161+ sendBatch batch
162+ atomically (readTChan recvChan) >>= putMVar resultMVar
163+ (batch, Nothing) -> do
164+ sendBatch batch
165+ atomically (readTChan recvChan) >>= \case
166+ SyncResult -> return ()
167+ ThrowJSValue e -> atomically (discardToSyncPoint commandChan) >>= (`putMVar` ThrowJSValue e)
168+ _ -> error "Unexpected result processing batch"
169+ return ()
170+ return (processResult, runReaderT (unJSM entryPoint) ctx)
159171 where
160− wsApp :: ServerApp
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)
179− , nextRef = nextRef
180− }
181− forkIO . forever $
182− receiveDataMessage conn >>= \case
183− (WS.Text t) ->
184− case decode t of
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"
206− return ()
207− runReaderT (unJSM entryPoint) ctx
208−
209172 readBatch :: TChan (Either AsyncCommand (Command, MVar Result)) -> IO (Batch, Maybe (MVar Result))
210173 readBatch chan = do
211174 first <- atomically $ readTChan chan -- We want at least one command to send
@@ -230,19 +193,4 @@ jsaddleOr opts entryPoint = websocketsOr opts wsApp
230193 readTChan chan >>= \case
231194 Right (_, resultMVar) -> return resultMVar
232195 _ -> discardToSyncPoint chan
233−
234−jsaddleApp :: Application
235−jsaddleApp req = jsaddleAppPieces (W.pathInfo req) req
236−
237−jsaddleAppPieces :: [Text] -> W.Application
238−jsaddleAppPieces _ req sendResponse
239− | notElem (W.requestMethod req) ["GET", "HEAD"] = sendResponse $ W.responseLBS
240− H.status405
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")
247− ] "Forbidden"
248196#endif
jsaddle/src/Language/Javascript/JSaddle/WebSockets/Files.hs → jsaddle/src/Language/Javascript/JSaddle/Run/Files.hsrenamed+51−38
@@ -11,12 +11,15 @@
1111--
1212-----------------------------------------------------------------------------
1313
14−module Language.Javascript.JSaddle.WebSockets.Files (
14+module Language.Javascript.JSaddle.Run.Files (
1515 indexHtml
1616 , jsaddleJs
17+ , initState
18+ , runBatch
1719) where
1820
1921import Data.ByteString.Lazy (ByteString)
22+import Data.Monoid ((<>))
2023
2124indexHtml :: ByteString
2225indexHtml =
@@ -30,24 +33,8 @@ indexHtml =
3033 \<script src=\"jsaddle.js\"></script>\n\
3134 \</html>"
3235
33−-- Use this to generate this string for embedding
34−-- sed -e 's|\\|\\\\|g' -e 's|^| \\|' -e 's|$|\\n\\|' -e 's|"|\\"|g' data/jsaddle.js | pbcopy
35−jsaddleJs :: ByteString
36−jsaddleJs = "\
37− \if(typeof global !== \"undefined\") {\n\
38− \ global.window = global;\n\
39− \ global.WebSocket = require('ws');\n\
40− \}\n\
41− \\n\
42− \var connect = function() {\n\
43− \ var wsaddress =\n\
44− \ typeof window.location === \"undefined\"\n\
45− \ ? \"ws://localhost:3709/\"\n\
46− \ : window.location.protocol.replace('http', 'ws')+\"//\"+window.location.hostname+(window.location.port?(\":\"+window.location.port):\"\");\n\
47− \\n\
48− \ var ws = new WebSocket(wsaddress);\n\
49− \\n\
50− \ ws.onopen = function(e) {\n\
36+initState :: ByteString
37+initState = "\
5138 \ var jsaddle_values = new Map();\n\
5239 \ jsaddle_values.set(0, null);\n\
5340 \ jsaddle_values.set(1, undefined);\n\
@@ -55,10 +42,10 @@ jsaddleJs = "\
5542 \ jsaddle_values.set(3, true);\n\
5643 \ jsaddle_values.set(4, window);\n\
5744 \ var jsaddle_index = 100;\n\
58− \\n\
59− \ ws.onmessage = function(e) {\n\
60− \ var batch = JSON.parse(e.data);\n\
61− \\n\
45+ \"
46+
47+runBatch :: (ByteString -> ByteString) -> ByteString
48+runBatch send = "\
6249 \ var processBatch = function(timestamp) {\n\
6350 \ try {\n\
6451 \ var nAsyncLength = batch[0].length;\n\
@@ -110,7 +97,7 @@ jsaddleJs = "\
11097 \ jsaddle_values.set(nArg, arguments[i]);\n\
11198 \ args[i] = nArg;\n\
11299 \ }\n\
113− \ ws.send(JSON.stringify({\"tag\": \"Callback\", \"contents\": [nFunction, nThis, args]}));\n\
100+ \ " <> send "{\"tag\": \"Callback\", \"contents\": [nFunction, nThis, args]}" <> "\n\
114101 \ })})();\n\
115102 \ break;\n\
116103 \ case \"CallAsFunction\":\n\
@@ -158,7 +145,7 @@ jsaddleJs = "\
158145 \ jsaddle_values.set(n, timestamp);\n\
159146 \ break;\n\
160147 \ default:\n\
161− \ ws.send(JSON.stringify({\"tag\": \"ProtocolError\", \"contents\": e.data}));\n\
148+ \ " <> send "{\"tag\": \"ProtocolError\", \"contents\": e.data}" <> "\n\
162149 \ return;\n\
163150 \ }\n\
164151 \ }\n\
@@ -167,17 +154,17 @@ jsaddleJs = "\
167154 \ case \"ValueToString\":\n\
168155 \ var val = jsaddle_values.get(d.contents);\n\
169156 \ var s = val === null ? \"null\" : val === undefined ? \"undefined\" : val.toString();\n\
170− \ ws.send(JSON.stringify({\"tag\": \"ValueToStringResult\", \"contents\": s}));\n\
157+ \ " <> send "{\"tag\": \"ValueToStringResult\", \"contents\": s}" <> "\n\
171158 \ break;\n\
172159 \ case \"ValueToBool\":\n\
173− \ ws.send(JSON.stringify({\"tag\": \"ValueToBoolResult\", \"contents\": jsaddle_values.get(d.contents) ? true : false}));\n\
160+ \ " <> send "{\"tag\": \"ValueToBoolResult\", \"contents\": jsaddle_values.get(d.contents) ? true : false}" <> "\n\
174161 \ break;\n\
175162 \ case \"ValueToNumber\":\n\
176− \ ws.send(JSON.stringify({\"tag\": \"ValueToNumberResult\", \"contents\": Number(jsaddle_values.get(d.contents))}));\n\
163+ \ " <> send "{\"tag\": \"ValueToNumberResult\", \"contents\": Number(jsaddle_values.get(d.contents))}" <> "\n\
177164 \ break;\n\
178165 \ case \"ValueToJSON\":\n\
179166 \ var s = jsaddle_values.get(d.contents) === undefined ? \"\" : JSON.stringify(jsaddle_values.get(d.contents));\n\
180− \ ws.send(JSON.stringify({\"tag\": \"ValueToJSONResult\", \"contents\": s}));\n\
167+ \ " <> send "{\"tag\": \"ValueToJSONResult\", \"contents\": s}" <> "\n\
181168 \ break;\n\
182169 \ case \"DeRefVal\":\n\
183170 \ var n = d.contents;\n\
@@ -189,36 +176,36 @@ jsaddleJs = "\
189176 \ (typeof v === \"number\") ? [-1, v.toString()] :\n\
190177 \ (typeof v === \"string\") ? [-2, v]\n\
191178 \ : [n, \"\"];\n\
192− \ ws.send(JSON.stringify({\"tag\": \"DeRefValResult\", \"contents\": c}));\n\
179+ \ " <> send "{\"tag\": \"DeRefValResult\", \"contents\": c}" <> "\n\
193180 \ break;\n\
194181 \ case \"IsNull\":\n\
195− \ ws.send(JSON.stringify({\"tag\": \"IsNullResult\", \"contents\": jsaddle_values.get(d.contents) === null}));\n\
182+ \ " <> send "{\"tag\": \"IsNullResult\", \"contents\": jsaddle_values.get(d.contents) === null}" <> "\n\
196183 \ break;\n\
197184 \ case \"IsUndefined\":\n\
198− \ ws.send(JSON.stringify({\"tag\": \"IsUndefinedResult\", \"contents\": jsaddle_values.get(d.contents) === undefined}));\n\
185+ \ " <> send "{\"tag\": \"IsUndefinedResult\", \"contents\": jsaddle_values.get(d.contents) === undefined}" <> "\n\
199186 \ break;\n\
200187 \ case \"InstanceOf\":\n\
201− \ ws.send(JSON.stringify({\"tag\": \"InstanceOfResult\", \"contents\": jsaddle_values.get(d.contents[0]) instanceof jsaddle_values.get(d.contents[1])}));\n\
188+ \ " <> send "{\"tag\": \"InstanceOfResult\", \"contents\": jsaddle_values.get(d.contents[0]) instanceof jsaddle_values.get(d.contents[1])}" <> "\n\
202189 \ break;\n\
203190 \ case \"StrictEqual\":\n\
204− \ ws.send(JSON.stringify({\"tag\": \"StrictEqualResult\", \"contents\": jsaddle_values.get(d.contents[0]) === jsaddle_values.get(d.contents[1])}));\n\
191+ \ " <> send "{\"tag\": \"StrictEqualResult\", \"contents\": jsaddle_values.get(d.contents[0]) === jsaddle_values.get(d.contents[1])}" <> "\n\
205192 \ break;\n\
206193 \ case \"PropertyNames\":\n\
207194 \ var result = [];\n\
208195 \ for (name in jsaddle_values.get(d.contents)) { result.push(name); }\n\
209− \ ws.send(JSON.stringify({\"tag\": \"PropertyNamesResult\", \"contents\": result}));\n\
196+ \ " <> send "{\"tag\": \"PropertyNamesResult\", \"contents\": result}" <> "\n\
210197 \ break;\n\
211198 \ case \"Sync\":\n\
212− \ ws.send(JSON.stringify({\"tag\": \"SyncResult\", \"contents\": []}));\n\
199+ \ " <> send "{\"tag\": \"SyncResult\", \"contents\": []}" <> "\n\
213200 \ break;\n\
214201 \ default:\n\
215− \ ws.send(JSON.stringify({\"tag\": \"ProtocolError\", \"contents\": e.data}));\n\
202+ \ " <> send "{\"tag\": \"ProtocolError\", \"contents\": e.data}" <> "\n\
216203 \ }\n\
217204 \ }\n\
218205 \ catch (err) {\n\
219206 \ var n = ++jsaddle_index;\n\
220207 \ jsaddle_values.set(n, err);\n\
221− \ ws.send(JSON.stringify({\"tag\": \"ThrowJSValue\", \"contents\": n}));\n\
208+ \ " <> send "{\"tag\": \"ThrowJSValue\", \"contents\": n}" <> "\n\
222209 \ }\n\
223210 \ };\n\
224211 \ if(batch[2]) {\n\
@@ -227,6 +214,32 @@ jsaddleJs = "\
227214 \ else {\n\
228215 \ processBatch(window.performance ? window.performance.now() : null);\n\
229216 \ }\n\
217+ \"
218+
219+-- Use this to generate this string for embedding
220+-- sed -e 's|\\|\\\\|g' -e 's|^| \\|' -e 's|$|\\n\\|' -e 's|"|\\"|g' data/jsaddle.js | pbcopy
221+jsaddleJs :: ByteString
222+jsaddleJs = "\
223+ \if(typeof global !== \"undefined\") {\n\
224+ \ global.window = global;\n\
225+ \ global.WebSocket = require('ws');\n\
226+ \}\n\
227+ \\n\
228+ \var connect = function() {\n\
229+ \ var wsaddress =\n\
230+ \ typeof window.location === \"undefined\"\n\
231+ \ ? \"ws://localhost:3709/\"\n\
232+ \ : window.location.protocol.replace('http', 'ws')+\"//\"+window.location.hostname+(window.location.port?(\":\"+window.location.port):\"\");\n\
233+ \\n\
234+ \ var ws = new WebSocket(wsaddress);\n\
235+ \\n\
236+ \ ws.onopen = function(e) {\n\
237+ \ " <> initState <> "\
238+ \\n\
239+ \ ws.onmessage = function(e) {\n\
240+ \ var batch = JSON.parse(e.data);\n\
241+ \\n\
242+ \ " <> runBatch (\a -> "ws.send(JSON.stringify(" <> a <> "));") <> "\
230243 \ };\n\
231244 \ };\n\
232245 \ ws.onerror = function() {\n\
jsaddle/src/Language/Javascript/JSaddle/Value.hs+4−5
@@ -70,8 +70,6 @@ module Language.Javascript.JSaddle.Value (
7070 , instanceOf
7171) where
7272
73−import Control.Applicative
74−import Prelude hiding (catch)
7573import Language.Javascript.JSaddle.Types
7674 (Object(..), JSString(..), JSVal(..))
7775#ifdef ghcjs_HOST_OS
@@ -87,7 +85,7 @@ import Data.JSString.Text (textToJSString)
8785import Language.Javascript.JSaddle.Native
8886 (wrapJSString, withJSVal, withObject, withJSString,
8987 withToJSVal)
90−import Language.Javascript.JSaddle.WebSockets
88+import Language.Javascript.JSaddle.Run
9189 (Command(..), AsyncCommand(..), Result(..), sendCommand,
9290 sendLazyCommand)
9391#endif
@@ -165,8 +163,8 @@ valToBool value =
165163 (JSVal 1) -> return False -- undefined
166164 (JSVal 2) -> return False -- false
167165 (JSVal 3) -> return True -- true
168− val ->
169− withJSVal val $ \rval -> do
166+ v ->
167+ withJSVal v $ \rval -> do
170168 ValueToBoolResult result <- sendCommand (ValueToBool rval)
171169 return result
172170#endif
@@ -570,6 +568,7 @@ deRefVal value = do
570568 DeRefValResult (-1) s -> return $ ValNumber (read (T.unpack s))
571569 DeRefValResult (-2) s -> return $ ValString s
572570 DeRefValResult ref _ -> return $ ValObject (Object (JSVal ref))
571+ _ -> error "Unexpected result dereferencing JSaddle value"
573572#endif
574573
575574-- | Make a JavaScript value out of a 'JSValue' ADT.
travis/build.sh+3−3
@@ -4,7 +4,7 @@ echo $PATH
44export LC_ALL=C.UTF-8
55GHCVER=`ghc --numeric-version`
66
7−JSADDLE_VERSION=`head -n2 jsaddle.cabal | tail -n1 | sed 's/[^0-9.]//g'`
7+JSADDLE_WARP_VERSION=`head -n2 jsaddle-warp/jsaddle-warp.cabal | tail -n1 | sed 's/[^0-9.]//g'`
88
99if [[ -d .cabal && -d .ghc ]]; then
1010 cp -a .cabal .ghc /root
@@ -13,8 +13,8 @@ fi
1313npm install ws
1414
1515cabal update
16−cabal new-build 'jsaddle:lib:jsaddle' 'jsaddle:test:test-tool'
17−GHC_PACKAGE_PATH=/opt/ghc/$GHCVER/lib/ghc-$GHCVER/package.conf.d:~/.cabal/store/ghc-$GHCVER/package.db:./dist-newstyle/packagedb/ghc-$GHCVER jsaddle_datadir=`pwd` ./dist-newstyle/build/jsaddle-$JSADDLE_VERSION/build/test-tool/test-tool
16+cabal new-build 'jsaddle-warp:lib:jsaddle-warp' 'jsaddle-warp:test:test-tool'
17+GHC_PACKAGE_PATH=/opt/ghc/$GHCVER/lib/ghc-$GHCVER/package.conf.d:~/.cabal/store/ghc-$GHCVER/package.db:./dist-newstyle/packagedb/ghc-$GHCVER jsaddle_datadir=`pwd` ./dist-newstyle/build/jsaddle-warp-$JSADDLE_WARP_VERSION/build/test-tool/test-tool .
1818
1919# update the cache
2020rm -rf .cabal