packages feed

ema-0.12.0.0: src/Ema/Server.hs

module Ema.Server (
  EmaWebSocketOptions (..),
  runServerWithWebSocketHotReload,
) where

import Control.Monad.Logger
import Data.LVar (LVar)
import Ema.CLI (Host (unHost))
import Ema.Route.Class (IsRoute (RouteModel))
import Ema.Server.HTTP (httpApp)
import Ema.Server.WebSocket (wsApp)
import Ema.Server.WebSocket.Options (EmaWebSocketOptions (..))
import Ema.Site (EmaStaticSite)
import Network.Wai qualified as Wai
import Network.Wai.Handler.Warp (Port)
import Network.Wai.Handler.Warp qualified as Warp
import Network.Wai.Handler.WebSockets qualified as WaiWs
import Network.WebSockets qualified as WS
import UnliftIO (MonadUnliftIO)
import UnliftIO.Concurrent (threadDelay)

runServerWithWebSocketHotReload ::
  forall r m.
  ( Show r
  , MonadIO m
  , MonadUnliftIO m
  , MonadLoggerIO m
  , Eq r
  , IsRoute r
  , EmaStaticSite r
  ) =>
  Maybe (EmaWebSocketOptions r) ->
  Host ->
  Maybe Port ->
  LVar (RouteModel r) ->
  m ()
runServerWithWebSocketHotReload mWsOpts host mport model = do
  logger <- askLoggerIO
  let runM = flip runLoggingT logger
      settings =
        Warp.defaultSettings
          & Warp.setHost (fromString . toString . unHost $ host)
      app =
        case mWsOpts of
          Nothing ->
            httpApp @r logger model Nothing
          Just opts ->
            WaiWs.websocketsOr
              WS.defaultConnectionOptions
              (wsApp @r logger model $ emaWebSocketServerHandler opts)
              (httpApp @r logger model $ Just $ emaWebSocketClientShim opts)
      banner port = do
        logInfoNS "ema" "==============================================="
        logInfoNS "ema" $ "Ema live server RUNNING: http://" <> unHost host <> ":" <> show port <> " (" <> maybe "no ws" (const "ws") mWsOpts <> ")"
        logInfoNS "ema" "==============================================="
  liftIO $ warpRunSettings settings mport (runM . banner) app

-- Like Warp.runSettings but takes *optional* port. When no port is set, a
-- free (random) port is used.
warpRunSettings :: Warp.Settings -> Maybe Port -> (Port -> IO a) -> Wai.Application -> IO ()
warpRunSettings settings mPort banner app = do
  case mPort of
    Nothing ->
      Warp.withApplicationSettings settings (pure app) $ \port -> do
        void $ banner port
        threadDelay maxBound
    Just port -> do
      void $ banner port
      Warp.runSettings (settings & Warp.setPort port) app