Monadic WS

This commit is contained in:
qvalentin 2023-02-18 16:57:20 +01:00
parent 3dc0135b48
commit 01df3f1068
3 changed files with 114 additions and 13 deletions

View File

@ -0,0 +1,40 @@
{-# LANGUAGE DeriveGeneric #-}
module Types.WebSocketMessages.WebSocketMessages
( WebSocketMessage (..),
SetClientInfo (..),
JoinRoom (..),
)
where
import ClassyPrelude
import Data.Aeson
( FromJSON (parseJSON),
Options (sumEncoding),
SumEncoding (..),
decode,
defaultOptions,
genericParseJSON,
withObject,
(.:),
)
data WebSocketMessage = ClientInfoMessage SetClientInfo | JoinRoomMessage JoinRoom
deriving (Generic)
instance FromJSON WebSocketMessage where
parseJSON = genericParseJSON defaultOptions {sumEncoding = UntaggedValue}
data SetClientInfo = SetClientInfo
{ displayName :: Text
}
deriving (Generic, Show)
instance FromJSON SetClientInfo
data JoinRoom = JoinRoom
{ roomName :: Text
}
deriving (Generic, Show)
instance FromJSON JoinRoom

View File

@ -7,7 +7,6 @@ module WebServer (runWebServer) where
import ClassyPrelude hiding (decodeUtf8) import ClassyPrelude hiding (decodeUtf8)
import Control.Monad.Except import Control.Monad.Except
import Data.Text.Encoding
import Network.HTTP.Types import Network.HTTP.Types
import Network.Wai import Network.Wai
import Network.Wai.Handler.Warp (run) import Network.Wai.Handler.Warp (run)
@ -44,8 +43,6 @@ newtype WebApp env a = WebApp {unWebApp :: env -> IO (Either ResponseReceived a)
) )
via ReaderT env (ExceptTApp ResponseReceived) via ReaderT env (ExceptTApp ResponseReceived)
-- via ExceptT ResponseReceived (ReaderT env IO)
getRequestPath :: getRequestPath ::
( MonadIO m, ( MonadIO m,
HasWebEnv env, HasWebEnv env,

View File

@ -1,11 +1,13 @@
{-# LANGUAGE DerivingVia #-} {-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE UndecidableInstances #-}
module WebSocket (runWebSocketServer) where module WebSocket (runWebSocketServer) where
import BroadcastUserData (broadcastUserData) import BroadcastUserData (broadcastUserData)
import ClassyPrelude import ClassyPrelude
import Data.Aeson
import Data.UUID (UUID) import Data.UUID (UUID)
import Data.UUID.V4 (nextRandom) import Data.UUID.V4 (nextRandom)
import Network.WebSockets qualified as WS import Network.WebSockets qualified as WS
@ -13,8 +15,15 @@ import Types.AppTypes
( Env (..), ( Env (..),
HasConnectedClientState (getConnectedClientState), HasConnectedClientState (getConnectedClientState),
) )
import Types.ConnectionState (Client (..), ConnectedClients) import Types.ConnectionState
( Client (..),
ConnectedClients,
)
import Types.RoomsState (HasRoomsState (..)) import Types.RoomsState (HasRoomsState (..))
import Types.WebSocketMessages.WebSocketMessages
( SetClientInfo (displayName),
WebSocketMessage (..),
)
data WSEnv = WSEnv data WSEnv = WSEnv
{ appEnv :: Env, { appEnv :: Env,
@ -81,6 +90,21 @@ joinRoom = do
let s' = updateClient clientId (\c -> c {joinedRoom = True}) s let s' = updateClient clientId (\c -> c {joinedRoom = True}) s
in return s' in return s'
updateClientName ::
( HasWSEnv env,
HasConnectedClientState env,
MonadReader env m,
MonadUnliftIO m
) =>
SetClientInfo ->
m ()
updateClientName clientInfo = do
clientId <- getClientId <$> ask
state <- getConnectedClientState <$> ask
modifyMVar_ state $ \s ->
let s' = updateClient clientId (\c -> c {name = displayName clientInfo}) s
in return s'
removeClient :: UUID -> ConnectedClients -> ConnectedClients removeClient :: UUID -> ConnectedClients -> ConnectedClients
removeClient toRemove = filter ((/= toRemove) . uuid) removeClient toRemove = filter ((/= toRemove) . uuid)
@ -121,11 +145,11 @@ newClient ::
HasWSEnv env, HasWSEnv env,
MonadReader env m MonadReader env m
) => ) =>
Text -> SetClientInfo ->
m Client m Client
newClient name = do newClient clientInfo = do
env <- ask env <- ask
return $ Client {uuid = getClientId env, name = name, conn = getConn env, joinedRoom = False} return $ Client {uuid = getClientId env, name = displayName clientInfo, conn = getConn env, joinedRoom = False}
newtype WSApp env a = WSApp {unWSApp :: env -> IO a} newtype WSApp env a = WSApp {unWSApp :: env -> IO a}
deriving deriving
@ -138,25 +162,54 @@ newtype WSApp env a = WSApp {unWSApp :: env -> IO a}
) )
via ReaderT env IO via ReaderT env IO
class Monad m => MonadWebSocket m where
getTypedWSMessage :: FromJSON a => m a
instance MonadWebSocket (WSApp WSEnv) where
getTypedWSMessage = do
msg <- getMessage
case eitherDecodeStrict $ encodeUtf8 msg of
Right a -> return a
Left err -> do
sendMessage $ "Bad message: " <> pack err
getTypedWSMessage
wsApp :: wsApp ::
( HasWSEnv env, ( HasWSEnv env,
HasConnectedClientState env, HasConnectedClientState env,
HasRoomsState env, HasRoomsState env,
MonadReader env m, MonadReader env m,
MonadUnliftIO m MonadUnliftIO m,
MonadWebSocket m
) => ) =>
m () m ()
wsApp = do wsApp = do
msg <- getMessage msg <- getTypedWSMessage
putStrLn msg print msg
client <- newClient msg client <- newClient msg
addWSClient client addWSClient client
broadcastUserData broadcastUserData
flip finally disconnectWsClient $ forever $ do flip finally disconnectWsClient $ forever $ do
currentMsg <- getMessage handleWSAction
joinRoom
broadcastUserData broadcastUserData
putStrLn currentMsg
handleWSAction ::
( HasWSEnv env,
HasConnectedClientState env,
HasRoomsState env,
MonadReader env m,
MonadUnliftIO m,
MonadWebSocket m
) =>
m ()
handleWSAction = do
msg <- getTypedWSMessage
case msg of
JoinRoomMessage _ -> do
joinRoom
return ()
ClientInfoMessage clientInfo -> do
updateClientName clientInfo
getMessage :: getMessage ::
( HasWSEnv env, ( HasWSEnv env,
@ -167,3 +220,14 @@ getMessage ::
getMessage = do getMessage = do
conn' <- getConn <$> ask conn' <- getConn <$> ask
liftIO $ WS.receiveData conn' liftIO $ WS.receiveData conn'
sendMessage ::
( HasWSEnv env,
MonadIO m,
MonadReader env m
) =>
Text ->
m ()
sendMessage msg = do
conn' <- getConn <$> ask
liftIO $ WS.sendTextData conn' msg