haste-app-0.1.0.0: src/Haste/App.hs
{-# LANGUAGE TypeFamilies, CPP, GeneralizedNewtypeDeriving, FlexibleContexts, ScopedTypeVariables #-}
module Haste.App
( module GHC.StaticPtr, module Data.Proxy, module Haste, module Haste.Serialize
, module Haste.App.Sandbox
, Endpoint, Node (..), CIO, Mapping (..), Dispatch
, MonadConc (..), MonadIO (..), MonadReader (..), MonadClient (..), MonadError (..)
, Remote, Remotable, RunsOn, Import, remote, dispatch, dispatchTo, annotate
, RemotePtr, Client, Server, EnvServer, NodeConfig, ClientError (..)
, runApp, start, startLocal, invokeServer
, using, localNode, remoteNode, remoteEndpoint, native
) where
import Control.Monad
import Control.Monad.Error
import Control.Monad.IO.Class
import Data.Proxy
import Data.Typeable
import Haste.Serialize
import Haste
import Haste.App.Remote
import Haste.App.Client
import Haste.App.Client.Type
import Haste.App.Protocol
import Haste.App.Routing as R
import Haste.Concurrent (MonadConc (..), CIO, concurrent)
import Haste.App.Sandbox hiding (callSandbox, createAppSandbox, initAppSandbox, isInSandbox)
import qualified Haste.App.Sandbox as Sbx (createAppSandbox, isInSandbox, initAppSandbox)
import Haste.App.Server
#ifndef __HASTE__
import Control.Concurrent (forkIO, threadDelay)
#endif
import GHC.StaticPtr
-- | A 'StaticPointer' to a remote import.
type RemotePtr dom = StaticPtr (Import dom)
-- | Start a server of the given node when this server binary starts.
start :: forall m. Node m => Proxy m -> NodeConfig
#ifdef __HASTE__
start p = do
case endpoint p of
LocalNode _ -> error "Please start local nodes using `startLocal'"
_ -> return ()
#else
start p = do
case endpoint p of
WebSocket _ port -> do
env <- R.init p
liftIO $ serverLoop (NodeEnv env :: NodeEnv m) port
_ -> return ()
#endif
startLocal :: forall m. (Perms m, Node m) => Proxy m -> NodeConfig
#ifdef __HASTE__
startLocal p = do
inSandbox <- liftIO Sbx.isInSandbox
case endpoint p of
LocalNode _
| inSandbox -> Sbx.initAppSandbox p
| otherwise -> Sbx.createAppSandbox p
_ -> return ()
#else
startLocal _ = do
return ()
#endif
-- | Run a Haste.App application. On the client side, a thread is forked off
-- to run the client part in isolation.
-- On the server side, one connection handler thread is forked off for each
-- endpoint. To conserve system resources, it is recommended to build one
-- server-side binary for each intended endpoint, with a single endpoint
-- handler for each. However, it is perfectly possible to build a single
-- server-side binary to handle *all* endpoints, and run that binary on
-- multiple machines.
runApp :: [NodeConfig] -> Client () -> IO ()
#ifdef __HASTE__
runApp eps m = do
inSandbox <- Sbx.isInSandbox
concurrent $ do
sequence_ eps
unless inSandbox $ runClient m
#else
runApp eps _ = mapM_ (forkIO . concurrent) eps >> zzz
where zzz = threadDelay (30*60*1000000) >> zzz
#endif
-- | Force the type of a monadic computation. Used to annotate inline remote
-- imports.
type RunsOn m = m ()
-- | Annotate a monadic computation with the node it's intended to run on.
-- This is often necessary when doing inline imports:
--
-- reverse_ :: String -> Client String
-- reverse_ = dispatch $ static (remote $ \x -> do
-- annotate :: RunsOn Server
-- return (reverse x)
-- )
--
-- This is essentially a more readable way to say @return () :: Server ()@.
annotate :: Monad m => RunsOn m
annotate = return ()
-- | Convenience function to use inline remote blocks.
-- Since remote functions rely on static pointers, inline blocks must not have
-- any free variables. Instead, this function explicitly captures any free
-- variables and constructs an explicit closure.
--
-- An example of usage:
-- > example :: Client ()
-- > example = do
-- > name <- prompt "What's your name?"
-- > age <- prompt "What's your age?"
-- > using (name, age) $ static (remote $ \(name, age) -> do
-- > annotate :: RunsOn Server
-- > JSString.putStrLn (JSString.concat ["name is ", age, " years old"])
-- > )
using fv f = dispatch f fv
-- | A local endpoint with a name derived from the fingerprint of the node
-- type. Guaranteed to be unique for each node.
localNode :: Typeable (m :: * -> *) => Proxy m -> Endpoint
localNode = LocalNode . show . typeRepFingerprint . typeRep
-- | Create a web socket endpoint.
remoteEndpoint :: String -> Int -> Proxy (m :: * -> *) -> Endpoint
remoteEndpoint host port _ = remoteNode host port
-- | Create a remote endpoint for use with 'dispatchTo'.
remoteNode :: String -> Int -> Endpoint
remoteNode = WebSocket
-- | Mark an expression as only being available on native nodes; i.e. the ones
-- not built with Haste. This is useful to ensure certain parts of an
-- application do not leak onto untrusted clients, as well as minimizing the
-- amount of JavaScript code that needs to be shipped to users.
native :: a -> a
#ifdef __HASTE__
native _ = error "expression only available on native nodes"
#else
native = id
#endif