Pure WS monad
This commit is contained in:
parent
5d3dced6f7
commit
865b69e799
|
@ -1,52 +1,51 @@
|
||||||
module BroadcastUserData
|
module BroadcastUserData
|
||||||
( broadcastUserData,
|
( broadcastUserData,
|
||||||
|
broadCastToClientsGeneric,
|
||||||
|
MonadBroadcast (..),
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
import Data.Aeson (encode)
|
import Data.Aeson (encode)
|
||||||
import Network.WebSockets qualified as WS
|
import qualified Network.WebSockets as WS
|
||||||
|
import State.ConnectedClientsState (MonadConnectedClientsRead (getConnctedClients))
|
||||||
|
import State.RoomDataState (MonadRoomDataStateRead (getRoomDataState))
|
||||||
import Types.AppTypes (HasConnectedClientState (..))
|
import Types.AppTypes (HasConnectedClientState (..))
|
||||||
import Types.ConnectionState (Client (..), ConnectedClients)
|
import Types.ConnectionState (Client (..), ConnectedClients)
|
||||||
import Types.RoomsState (HasRoomsState (..))
|
import Types.RoomsState (HasRoomsState (..))
|
||||||
import Types.UsersData (UsersData (..))
|
import Types.UsersData (UsersData (..))
|
||||||
|
|
||||||
|
class (Monad m, MonadConnectedClientsRead m) => MonadBroadcast m where
|
||||||
|
broadCastToClients :: Text -> m ()
|
||||||
|
|
||||||
broadcastUserData ::
|
broadcastUserData ::
|
||||||
( MonadIO m,
|
( MonadRoomDataStateRead m,
|
||||||
HasConnectedClientState env,
|
MonadBroadcast m
|
||||||
HasRoomsState env,
|
|
||||||
MonadReader env m
|
|
||||||
) =>
|
) =>
|
||||||
m ()
|
m ()
|
||||||
broadcastUserData = do
|
broadcastUserData = do
|
||||||
putStrLn "broadcasting"
|
|
||||||
userWithOutRoom <- getUsersWithoutRoom
|
userWithOutRoom <- getUsersWithoutRoom
|
||||||
roomsData <- ask >>= readMVar . getRoomsState
|
roomsData <- getRoomDataState
|
||||||
let usersData = UsersData {usersWithOutRoom = userWithOutRoom, roomsData = roomsData}
|
let usersData = UsersData {usersWithOutRoom = userWithOutRoom, roomsData = roomsData}
|
||||||
broadCastToClients $ (decodeUtf8 . toStrict . encode) usersData
|
broadCastToClients $ (decodeUtf8 . toStrict . encode) usersData
|
||||||
|
|
||||||
getUsersWithoutRoom ::
|
getUsersWithoutRoom ::
|
||||||
( MonadIO m,
|
( MonadConnectedClientsRead m
|
||||||
HasConnectedClientState env,
|
|
||||||
MonadReader env m
|
|
||||||
) =>
|
) =>
|
||||||
m [Text]
|
m [Text]
|
||||||
getUsersWithoutRoom = do
|
getUsersWithoutRoom = map name . filter (not . joinedRoom) <$> getConnctedClients
|
||||||
state <- ask >>= readMVar . getConnectedClientState
|
|
||||||
return $ map name $ filter (not . joinedRoom) state
|
broadCastToClientsGeneric ::
|
||||||
|
( MonadIO m,
|
||||||
|
MonadConnectedClientsRead m
|
||||||
|
) =>
|
||||||
|
Text ->
|
||||||
|
m ()
|
||||||
|
broadCastToClientsGeneric message = do
|
||||||
|
state <- getConnctedClients
|
||||||
|
liftIO $ broadcast message state
|
||||||
|
|
||||||
broadcast :: Text -> ConnectedClients -> IO ()
|
broadcast :: Text -> ConnectedClients -> IO ()
|
||||||
broadcast message clients = do
|
broadcast message clients = do
|
||||||
putStrLn message
|
putStrLn message
|
||||||
forM_ clients $ \client -> WS.sendTextData (conn client) message
|
forM_ clients $ \client -> WS.sendTextData (conn client) message
|
||||||
|
|
||||||
broadCastToClients ::
|
|
||||||
( MonadIO m,
|
|
||||||
HasConnectedClientState env,
|
|
||||||
MonadReader env m
|
|
||||||
) =>
|
|
||||||
Text ->
|
|
||||||
m ()
|
|
||||||
broadCastToClients message = do
|
|
||||||
state <- getConnectedClientState <$> ask
|
|
||||||
liftIO $ withMVar state $ \currenState -> broadcast message currenState
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import Types.AppTypes
|
||||||
import Types.ConnectionState (initConnectionsState)
|
import Types.ConnectionState (initConnectionsState)
|
||||||
import Types.RoomsState (initRoomsState)
|
import Types.RoomsState (initRoomsState)
|
||||||
import WebServer (runWebServer)
|
import WebServer (runWebServer)
|
||||||
import WebSocket (runWebSocketServer)
|
import WebSocket.Server (runWebSocketServer)
|
||||||
|
|
||||||
runBothServers :: IO ()
|
runBothServers :: IO ()
|
||||||
runBothServers = do
|
runBothServers = do
|
||||||
|
|
|
@ -5,7 +5,7 @@ module RoomDataHandler
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import BroadcastUserData (broadcastUserData)
|
import BroadcastUserData (MonadBroadcast, broadcastUserData)
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
import Control.Monad.Except (MonadError, throwError)
|
import Control.Monad.Except (MonadError, throwError)
|
||||||
import Data.Aeson (eitherDecodeStrict)
|
import Data.Aeson (eitherDecodeStrict)
|
||||||
|
@ -16,27 +16,36 @@ import Network.Wai
|
||||||
consumeRequestBodyStrict,
|
consumeRequestBodyStrict,
|
||||||
responseLBS,
|
responseLBS,
|
||||||
)
|
)
|
||||||
|
import State.ConnectedClientsState (MonadConnectedClientsRead)
|
||||||
|
import State.RoomDataState
|
||||||
|
( MonadRoomDataStateModify (setRoomDataState),
|
||||||
|
MonadRoomDataStateRead,
|
||||||
|
)
|
||||||
import Types.AppTypes (HasConnectedClientState)
|
import Types.AppTypes (HasConnectedClientState)
|
||||||
import Types.RoomsState
|
import Types.RoomsState
|
||||||
( HasRoomsState,
|
( HasRoomsState,
|
||||||
roomStateDiffers,
|
roomStateDiffers,
|
||||||
updateRoomState,
|
updateRoomState,
|
||||||
)
|
)
|
||||||
import Types.WebEnv (HasWebEnv (getRequest), getRespond)
|
import Types.WebEnv
|
||||||
|
( HasWebEnv (getRequest),
|
||||||
|
getRespond,
|
||||||
|
)
|
||||||
|
|
||||||
roomDataHandler ::
|
roomDataHandler ::
|
||||||
( MonadIO m,
|
( MonadIO m,
|
||||||
HasWebEnv env,
|
HasWebEnv env,
|
||||||
HasConnectedClientState env,
|
|
||||||
MonadReader env m,
|
MonadReader env m,
|
||||||
MonadError ResponseReceived m,
|
MonadError ResponseReceived m,
|
||||||
HasRoomsState env
|
MonadRoomDataStateRead m,
|
||||||
|
MonadRoomDataStateModify m,
|
||||||
|
MonadBroadcast m
|
||||||
) =>
|
) =>
|
||||||
m ResponseReceived
|
m ResponseReceived
|
||||||
roomDataHandler = do
|
roomDataHandler = do
|
||||||
newRoomData <- parseBodyOrBadRequest
|
newRoomData <- parseBodyOrBadRequest
|
||||||
whenM (roomStateDiffers newRoomData) $ do
|
whenM (roomStateDiffers newRoomData) $ do
|
||||||
updateRoomState newRoomData
|
setRoomDataState newRoomData
|
||||||
broadcastUserData
|
broadcastUserData
|
||||||
success
|
success
|
||||||
|
|
||||||
|
|
|
@ -82,7 +82,7 @@ class Monad m => MonadConnectedClientsRead m where
|
||||||
getConnctedClientsGeneric ::
|
getConnctedClientsGeneric ::
|
||||||
( HasConnectedClientState env,
|
( HasConnectedClientState env,
|
||||||
MonadReader env m,
|
MonadReader env m,
|
||||||
MonadUnliftIO m
|
MonadIO m
|
||||||
) =>
|
) =>
|
||||||
m ConnectedClients
|
m ConnectedClients
|
||||||
getConnctedClientsGeneric = do
|
getConnctedClientsGeneric = do
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
module State.RoomDataState
|
||||||
|
( MonadRoomDataStateRead (..),
|
||||||
|
MonadRoomDataStateModify (..),
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import ClassyPrelude
|
||||||
|
import Types.RoomData
|
||||||
|
|
||||||
|
class Monad m => MonadRoomDataStateModify m where
|
||||||
|
setRoomDataState :: RoomsData -> m ()
|
||||||
|
|
||||||
|
class Monad m => MonadRoomDataStateRead m where
|
||||||
|
getRoomDataState :: m RoomsData
|
|
@ -4,10 +4,12 @@ module Types.RoomsState
|
||||||
HasRoomsState (..),
|
HasRoomsState (..),
|
||||||
roomStateDiffers,
|
roomStateDiffers,
|
||||||
updateRoomState,
|
updateRoomState,
|
||||||
|
getRoomState,
|
||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
|
import State.RoomDataState (MonadRoomDataStateRead (getRoomDataState))
|
||||||
import Types.RoomData (RoomsData)
|
import Types.RoomData (RoomsData)
|
||||||
|
|
||||||
type RoomsState = MVar RoomsData
|
type RoomsState = MVar RoomsData
|
||||||
|
@ -30,16 +32,23 @@ updateRoomState newData = do
|
||||||
_ <- swapMVar state newData
|
_ <- swapMVar state newData
|
||||||
return ()
|
return ()
|
||||||
|
|
||||||
roomStateDiffers ::
|
getRoomState ::
|
||||||
( HasRoomsState env,
|
( HasRoomsState env,
|
||||||
MonadIO m,
|
MonadIO m,
|
||||||
MonadReader env m
|
MonadReader env m
|
||||||
) =>
|
) =>
|
||||||
|
m RoomsData
|
||||||
|
getRoomState = do
|
||||||
|
state <- getRoomsState <$> ask
|
||||||
|
readMVar state
|
||||||
|
|
||||||
|
roomStateDiffers ::
|
||||||
|
( MonadRoomDataStateRead m
|
||||||
|
) =>
|
||||||
RoomsData ->
|
RoomsData ->
|
||||||
m Bool
|
m Bool
|
||||||
roomStateDiffers newData = do
|
roomStateDiffers newData = do
|
||||||
currentData <- ask >>= readMVar . getRoomsState
|
not . eqIgnoreOrdering newData <$> getRoomDataState
|
||||||
return $ not $ eqIgnoreOrdering newData currentData
|
|
||||||
|
|
||||||
eqIgnoreOrdering :: (Eq a) => [a] -> [a] -> Bool
|
eqIgnoreOrdering :: (Eq a) => [a] -> [a] -> Bool
|
||||||
eqIgnoreOrdering a b = length a == length b && all (`elem` b) a
|
eqIgnoreOrdering a b = length a == length b && all (`elem` b) a
|
||||||
|
|
|
@ -1,22 +1,33 @@
|
||||||
{-# LANGUAGE AllowAmbiguousTypes #-}
|
{-# LANGUAGE AllowAmbiguousTypes #-}
|
||||||
{-# LANGUAGE DerivingVia #-}
|
{-# LANGUAGE DerivingVia #-}
|
||||||
{-# LANGUAGE FlexibleContexts #-}
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
{-# LANGUAGE LambdaCase #-}
|
{-# LANGUAGE LambdaCase #-}
|
||||||
|
|
||||||
module WebServer (runWebServer) where
|
module WebServer (runWebServer) where
|
||||||
|
|
||||||
|
import BroadcastUserData
|
||||||
|
( MonadBroadcast (..),
|
||||||
|
broadCastToClientsGeneric,
|
||||||
|
)
|
||||||
import ClassyPrelude hiding (decodeUtf8)
|
import ClassyPrelude hiding (decodeUtf8)
|
||||||
import Control.Monad.Except
|
import Control.Monad.Except
|
||||||
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)
|
||||||
import RoomDataHandler (roomDataHandler)
|
import RoomDataHandler (roomDataHandler)
|
||||||
import Types.AppTypes
|
import State.ConnectedClientsState
|
||||||
( Env (..),
|
( MonadConnectedClientsRead (..),
|
||||||
HasConnectedClientState (..),
|
getConnctedClientsGeneric,
|
||||||
)
|
)
|
||||||
|
import State.RoomDataState
|
||||||
|
( MonadRoomDataStateModify (..),
|
||||||
|
MonadRoomDataStateRead (getRoomDataState),
|
||||||
|
)
|
||||||
|
import Types.AppTypes (Env (..))
|
||||||
import Types.RoomsState
|
import Types.RoomsState
|
||||||
( HasRoomsState (getRoomsState),
|
( HasRoomsState (getRoomsState),
|
||||||
|
getRoomState,
|
||||||
roomStateDiffers,
|
roomStateDiffers,
|
||||||
updateRoomState,
|
updateRoomState,
|
||||||
)
|
)
|
||||||
|
@ -43,6 +54,18 @@ newtype WebApp env a = WebApp {unWebApp :: env -> IO (Either ResponseReceived a)
|
||||||
)
|
)
|
||||||
via ReaderT env (ExceptTApp ResponseReceived)
|
via ReaderT env (ExceptTApp ResponseReceived)
|
||||||
|
|
||||||
|
instance MonadConnectedClientsRead (WebApp WebEnv) where
|
||||||
|
getConnctedClients = getConnctedClientsGeneric
|
||||||
|
|
||||||
|
instance MonadRoomDataStateModify (WebApp WebEnv) where
|
||||||
|
setRoomDataState = updateRoomState
|
||||||
|
|
||||||
|
instance MonadRoomDataStateRead (WebApp WebEnv) where
|
||||||
|
getRoomDataState = getRoomState
|
||||||
|
|
||||||
|
instance MonadBroadcast (WebApp WebEnv) where
|
||||||
|
broadCastToClients = broadCastToClientsGeneric
|
||||||
|
|
||||||
getRequestPath ::
|
getRequestPath ::
|
||||||
( MonadIO m,
|
( MonadIO m,
|
||||||
HasWebEnv env,
|
HasWebEnv env,
|
||||||
|
@ -56,10 +79,11 @@ getRequestPath = do
|
||||||
app ::
|
app ::
|
||||||
( MonadIO m,
|
( MonadIO m,
|
||||||
HasWebEnv env,
|
HasWebEnv env,
|
||||||
HasConnectedClientState env,
|
|
||||||
MonadReader env m,
|
MonadReader env m,
|
||||||
HasRoomsState env,
|
MonadError ResponseReceived m,
|
||||||
MonadError ResponseReceived m
|
MonadRoomDataStateModify m,
|
||||||
|
MonadRoomDataStateRead m,
|
||||||
|
MonadBroadcast m
|
||||||
) =>
|
) =>
|
||||||
m ResponseReceived
|
m ResponseReceived
|
||||||
app = requestPathHandler
|
app = requestPathHandler
|
||||||
|
@ -67,10 +91,11 @@ app = requestPathHandler
|
||||||
requestPathHandler ::
|
requestPathHandler ::
|
||||||
( MonadIO m,
|
( MonadIO m,
|
||||||
HasWebEnv env,
|
HasWebEnv env,
|
||||||
HasConnectedClientState env,
|
|
||||||
MonadReader env m,
|
MonadReader env m,
|
||||||
HasRoomsState env,
|
MonadError ResponseReceived m,
|
||||||
MonadError ResponseReceived m
|
MonadRoomDataStateModify m,
|
||||||
|
MonadRoomDataStateRead m,
|
||||||
|
MonadBroadcast m
|
||||||
) =>
|
) =>
|
||||||
m ResponseReceived
|
m ResponseReceived
|
||||||
requestPathHandler = do
|
requestPathHandler = do
|
||||||
|
|
|
@ -3,178 +3,43 @@
|
||||||
{-# LANGUAGE FlexibleInstances #-}
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
{-# LANGUAGE UndecidableInstances #-}
|
{-# LANGUAGE UndecidableInstances #-}
|
||||||
|
|
||||||
module WebSocket (runWebSocketServer) where
|
module WebSocket (WSEnv (..), wsApp, WSApp (..)) where
|
||||||
|
|
||||||
import BroadcastUserData (broadcastUserData)
|
import BroadcastUserData
|
||||||
|
( MonadBroadcast (..),
|
||||||
|
broadcastUserData,
|
||||||
|
)
|
||||||
import ClassyPrelude
|
import ClassyPrelude
|
||||||
import Data.Aeson
|
import Data.UUID.V4 ()
|
||||||
import Data.UUID (UUID)
|
import State.ConnectedClientsState (MonadConnectedClientsModify (..))
|
||||||
import Data.UUID.V4 (nextRandom)
|
import State.RoomDataState (MonadRoomDataStateRead (..))
|
||||||
import Network.WebSockets qualified as WS
|
import Types.ConnectionState (Client (..))
|
||||||
import State.ConnectedClientsState
|
|
||||||
( MonadConnectedClientsModify (..),
|
|
||||||
MonadConnectedClientsRead (getConnctedClients),
|
|
||||||
addWSClientGeneric,
|
|
||||||
getConnctedClientsGeneric,
|
|
||||||
removeWSClientGeneric,
|
|
||||||
updateWSClientGeneric,
|
|
||||||
)
|
|
||||||
import Types.AppTypes
|
|
||||||
( Env (..),
|
|
||||||
HasConnectedClientState (getConnectedClientState),
|
|
||||||
)
|
|
||||||
import Types.ConnectionState
|
|
||||||
( Client (..),
|
|
||||||
ConnectedClients,
|
|
||||||
)
|
|
||||||
import Types.RoomsState (HasRoomsState (..))
|
|
||||||
import Types.WebSocketMessages.WebSocketMessages
|
import Types.WebSocketMessages.WebSocketMessages
|
||||||
( SetClientInfo (displayName),
|
( SetClientInfo (displayName),
|
||||||
WebSocketMessage (..),
|
WebSocketMessage (..),
|
||||||
)
|
)
|
||||||
|
import WebSocket.MonadWebSocketSession
|
||||||
data WSEnv = WSEnv
|
import WebSocket.WSReaderTApp
|
||||||
{ appEnv :: Env,
|
|
||||||
connection :: WS.Connection,
|
|
||||||
clientId :: UUID
|
|
||||||
}
|
|
||||||
|
|
||||||
instance HasConnectedClientState WSEnv where
|
|
||||||
getConnectedClientState = connectedClientsState . appEnv
|
|
||||||
|
|
||||||
instance HasRoomsState WSEnv where
|
|
||||||
getRoomsState = roomsState . appEnv
|
|
||||||
|
|
||||||
class HasWSEnv a where
|
|
||||||
getConn :: a -> WS.Connection
|
|
||||||
getClientId :: a -> UUID
|
|
||||||
|
|
||||||
instance HasWSEnv WSEnv where
|
|
||||||
getConn = connection
|
|
||||||
getClientId = clientId
|
|
||||||
|
|
||||||
joinRoom ::
|
|
||||||
( MonadReader env m,
|
|
||||||
MonadConnectedClientsModify m,
|
|
||||||
HasWSEnv env
|
|
||||||
) =>
|
|
||||||
m ()
|
|
||||||
joinRoom = do
|
|
||||||
clientId <- getClientId <$> ask
|
|
||||||
updateWSClient clientId (\c -> c {joinedRoom = True})
|
|
||||||
|
|
||||||
updateClientName ::
|
|
||||||
( HasWSEnv env,
|
|
||||||
MonadReader env m,
|
|
||||||
MonadConnectedClientsModify m
|
|
||||||
) =>
|
|
||||||
SetClientInfo ->
|
|
||||||
m ()
|
|
||||||
updateClientName clientInfo = do
|
|
||||||
clientId <- getClientId <$> ask
|
|
||||||
updateWSClient clientId (\c -> c {name = displayName clientInfo})
|
|
||||||
|
|
||||||
disconnectWsClient ::
|
|
||||||
( HasWSEnv env,
|
|
||||||
MonadConnectedClientsModify m,
|
|
||||||
MonadReader env m
|
|
||||||
) =>
|
|
||||||
m ()
|
|
||||||
disconnectWsClient = do
|
|
||||||
clientId <- getClientId <$> ask
|
|
||||||
removeWSClient clientId
|
|
||||||
|
|
||||||
runWebSocketServer ::
|
|
||||||
( MonadIO m,
|
|
||||||
MonadReader Env m
|
|
||||||
) =>
|
|
||||||
m ()
|
|
||||||
runWebSocketServer = do
|
|
||||||
putStrLn "Websocket up at 127.0.0.1:9160"
|
|
||||||
wsApp' <- runWSApp
|
|
||||||
liftIO $ WS.runServer "127.0.0.1" 9160 wsApp'
|
|
||||||
|
|
||||||
runWSApp ::
|
|
||||||
( MonadIO m,
|
|
||||||
MonadReader Env m
|
|
||||||
) =>
|
|
||||||
m WS.ServerApp
|
|
||||||
runWSApp = do
|
|
||||||
env <- ask
|
|
||||||
return
|
|
||||||
( \pending -> do
|
|
||||||
conn <- WS.acceptRequest pending
|
|
||||||
uuid <- nextRandom
|
|
||||||
let wsEnv = WSEnv {appEnv = env, connection = conn, clientId = uuid}
|
|
||||||
WS.withPingThread conn 30 (return ()) $ unWSApp wsApp wsEnv
|
|
||||||
)
|
|
||||||
|
|
||||||
newClient ::
|
|
||||||
( MonadIO m,
|
|
||||||
HasWSEnv env,
|
|
||||||
MonadReader env m
|
|
||||||
) =>
|
|
||||||
SetClientInfo ->
|
|
||||||
m Client
|
|
||||||
newClient clientInfo = do
|
|
||||||
env <- ask
|
|
||||||
return $ Client {uuid = getClientId env, name = displayName clientInfo, conn = getConn env, joinedRoom = False}
|
|
||||||
|
|
||||||
newtype WSApp env a = WSApp {unWSApp :: env -> IO a}
|
|
||||||
deriving
|
|
||||||
( Functor,
|
|
||||||
Applicative,
|
|
||||||
Monad,
|
|
||||||
MonadReader env,
|
|
||||||
MonadIO,
|
|
||||||
MonadUnliftIO
|
|
||||||
)
|
|
||||||
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
|
|
||||||
|
|
||||||
instance MonadConnectedClientsModify (WSApp WSEnv) where
|
|
||||||
addWSClient = addWSClientGeneric
|
|
||||||
updateWSClient = updateWSClientGeneric
|
|
||||||
removeWSClient = removeWSClientGeneric
|
|
||||||
|
|
||||||
instance MonadConnectedClientsRead (WSApp WSEnv) where
|
|
||||||
getConnctedClients = getConnctedClientsGeneric
|
|
||||||
|
|
||||||
wsApp ::
|
wsApp ::
|
||||||
( HasWSEnv env,
|
( MonadWebSocketSession m,
|
||||||
HasConnectedClientState env,
|
MonadWebSocketSessionInit m,
|
||||||
HasRoomsState env,
|
MonadConnectedClientsModify m,
|
||||||
MonadReader env m,
|
MonadRoomDataStateRead m,
|
||||||
MonadUnliftIO m,
|
MonadBroadcast m
|
||||||
MonadWebSocket m,
|
|
||||||
MonadConnectedClientsModify m
|
|
||||||
) =>
|
) =>
|
||||||
m ()
|
m ()
|
||||||
wsApp = do
|
wsApp = do
|
||||||
msg <- getTypedWSMessage
|
msg <- getTypedWSMessage
|
||||||
print msg
|
|
||||||
client <- newClient msg
|
client <- newClient msg
|
||||||
addWSClient client
|
addWSClient client
|
||||||
broadcastUserData
|
broadcastUserData
|
||||||
flip finally disconnectWsClient $ forever $ do
|
withCleanUp $ forever $ do
|
||||||
handleWSAction
|
handleWSAction
|
||||||
broadcastUserData
|
broadcastUserData
|
||||||
|
|
||||||
handleWSAction ::
|
handleWSAction ::
|
||||||
( HasWSEnv env,
|
( MonadWebSocketSession m,
|
||||||
MonadReader env m,
|
|
||||||
MonadWebSocket m,
|
|
||||||
MonadConnectedClientsModify m
|
MonadConnectedClientsModify m
|
||||||
) =>
|
) =>
|
||||||
m ()
|
m ()
|
||||||
|
@ -183,27 +48,24 @@ handleWSAction = do
|
||||||
case msg of
|
case msg of
|
||||||
JoinRoomMessage _ -> do
|
JoinRoomMessage _ -> do
|
||||||
joinRoom
|
joinRoom
|
||||||
return ()
|
|
||||||
ClientInfoMessage clientInfo -> do
|
ClientInfoMessage clientInfo -> do
|
||||||
updateClientName clientInfo
|
updateClientName clientInfo
|
||||||
|
|
||||||
getMessage ::
|
joinRoom ::
|
||||||
( HasWSEnv env,
|
( MonadConnectedClientsModify m,
|
||||||
MonadIO m,
|
MonadWebSocketSession m
|
||||||
MonadReader env m
|
|
||||||
) =>
|
) =>
|
||||||
m Text
|
|
||||||
getMessage = do
|
|
||||||
conn' <- getConn <$> ask
|
|
||||||
liftIO $ WS.receiveData conn'
|
|
||||||
|
|
||||||
sendMessage ::
|
|
||||||
( HasWSEnv env,
|
|
||||||
MonadIO m,
|
|
||||||
MonadReader env m
|
|
||||||
) =>
|
|
||||||
Text ->
|
|
||||||
m ()
|
m ()
|
||||||
sendMessage msg = do
|
joinRoom = do
|
||||||
conn' <- getConn <$> ask
|
clientId <- getSesssionId
|
||||||
liftIO $ WS.sendTextData conn' msg
|
updateWSClient clientId (\c -> c {joinedRoom = True})
|
||||||
|
|
||||||
|
updateClientName ::
|
||||||
|
( MonadWebSocketSession m,
|
||||||
|
MonadConnectedClientsModify m
|
||||||
|
) =>
|
||||||
|
SetClientInfo ->
|
||||||
|
m ()
|
||||||
|
updateClientName clientInfo = do
|
||||||
|
clientId <- getSesssionId
|
||||||
|
updateWSClient clientId (\c -> c {name = displayName clientInfo})
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
module WebSocket.Messages
|
||||||
|
( getMessage,
|
||||||
|
sendMessage,
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import ClassyPrelude
|
||||||
|
import Network.WebSockets qualified as WS
|
||||||
|
import WebSocket.WSReaderTApp
|
||||||
|
|
||||||
|
getMessage ::
|
||||||
|
( HasWSEnv env,
|
||||||
|
MonadIO m,
|
||||||
|
MonadReader env m
|
||||||
|
) =>
|
||||||
|
m Text
|
||||||
|
getMessage = do
|
||||||
|
conn' <- getConn <$> ask
|
||||||
|
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
|
|
@ -0,0 +1,64 @@
|
||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
|
||||||
|
module WebSocket.MonadWebSocketSession
|
||||||
|
( MonadWebSocketSession (..),
|
||||||
|
MonadWebSocketSessionInit (..),
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import ClassyPrelude
|
||||||
|
import Data.Aeson
|
||||||
|
( FromJSON,
|
||||||
|
eitherDecodeStrict,
|
||||||
|
)
|
||||||
|
import Data.UUID (UUID)
|
||||||
|
import State.ConnectedClientsState
|
||||||
|
( MonadConnectedClientsModify,
|
||||||
|
removeWSClient,
|
||||||
|
)
|
||||||
|
import Types.ConnectionState (Client (..))
|
||||||
|
import Types.WebSocketMessages.WebSocketMessages (SetClientInfo (..))
|
||||||
|
import WebSocket.Messages
|
||||||
|
import WebSocket.WSReaderTApp
|
||||||
|
|
||||||
|
class Monad m => MonadWebSocketSession m where
|
||||||
|
getTypedWSMessage :: FromJSON a => m a
|
||||||
|
getSesssionId :: m UUID
|
||||||
|
|
||||||
|
instance MonadWebSocketSession (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
|
||||||
|
getSesssionId = getClientId <$> ask
|
||||||
|
|
||||||
|
class (Monad m) => MonadWebSocketSessionInit m where
|
||||||
|
newClient :: SetClientInfo -> m Client
|
||||||
|
withCleanUp :: m () -> m ()
|
||||||
|
|
||||||
|
instance MonadWebSocketSessionInit (WSApp WSEnv) where
|
||||||
|
newClient = newClientGeneric
|
||||||
|
withCleanUp = flip finally disconnectWsClient
|
||||||
|
|
||||||
|
newClientGeneric ::
|
||||||
|
( MonadIO m,
|
||||||
|
HasWSEnv env,
|
||||||
|
MonadReader env m
|
||||||
|
) =>
|
||||||
|
SetClientInfo ->
|
||||||
|
m Client
|
||||||
|
newClientGeneric clientInfo = do
|
||||||
|
env <- ask
|
||||||
|
return $ Client {uuid = getClientId env, name = displayName clientInfo, conn = getConn env, joinedRoom = False}
|
||||||
|
|
||||||
|
disconnectWsClient ::
|
||||||
|
( MonadConnectedClientsModify m,
|
||||||
|
MonadWebSocketSession m
|
||||||
|
) =>
|
||||||
|
m ()
|
||||||
|
disconnectWsClient = do
|
||||||
|
clientId <- getSesssionId
|
||||||
|
removeWSClient clientId
|
|
@ -0,0 +1,39 @@
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
|
||||||
|
module WebSocket.Server
|
||||||
|
( runWebSocketServer,
|
||||||
|
runWSApp,
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import ClassyPrelude
|
||||||
|
import Data.UUID.V4 (nextRandom)
|
||||||
|
import Network.WebSockets qualified as WS
|
||||||
|
import Types.AppTypes
|
||||||
|
import WebSocket
|
||||||
|
import WebSocket (WSApp (..))
|
||||||
|
|
||||||
|
runWebSocketServer ::
|
||||||
|
( MonadIO m,
|
||||||
|
MonadReader Env m
|
||||||
|
) =>
|
||||||
|
m ()
|
||||||
|
runWebSocketServer = do
|
||||||
|
putStrLn "Websocket up at 127.0.0.1:9160"
|
||||||
|
wsApp' <- runWSApp
|
||||||
|
liftIO $ WS.runServer "127.0.0.1" 9160 wsApp'
|
||||||
|
|
||||||
|
runWSApp ::
|
||||||
|
( MonadIO m,
|
||||||
|
MonadReader Env m
|
||||||
|
) =>
|
||||||
|
m WS.ServerApp
|
||||||
|
runWSApp = do
|
||||||
|
env <- ask
|
||||||
|
return
|
||||||
|
( \pending -> do
|
||||||
|
conn <- WS.acceptRequest pending
|
||||||
|
uuid <- nextRandom
|
||||||
|
let wsEnv = WSEnv {appEnv = env, connection = conn, clientId = uuid}
|
||||||
|
WS.withPingThread conn 30 (return ()) $ unWSApp wsApp wsEnv
|
||||||
|
)
|
|
@ -0,0 +1,74 @@
|
||||||
|
{-# LANGUAGE DerivingVia #-}
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
{-# LANGUAGE FlexibleInstances #-}
|
||||||
|
|
||||||
|
module WebSocket.WSReaderTApp
|
||||||
|
( WSApp (..),
|
||||||
|
WSEnv (..),
|
||||||
|
HasWSEnv (..),
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import BroadcastUserData
|
||||||
|
( MonadBroadcast (..),
|
||||||
|
broadCastToClientsGeneric,
|
||||||
|
)
|
||||||
|
import ClassyPrelude
|
||||||
|
import Data.UUID
|
||||||
|
import Network.WebSockets qualified as WS
|
||||||
|
import State.ConnectedClientsState
|
||||||
|
( MonadConnectedClientsModify (..),
|
||||||
|
MonadConnectedClientsRead (..),
|
||||||
|
addWSClientGeneric,
|
||||||
|
getConnctedClientsGeneric,
|
||||||
|
removeWSClientGeneric,
|
||||||
|
updateWSClientGeneric,
|
||||||
|
)
|
||||||
|
import State.RoomDataState
|
||||||
|
import Types.AppTypes
|
||||||
|
import Types.RoomsState (HasRoomsState (..), getRoomState)
|
||||||
|
|
||||||
|
data WSEnv = WSEnv
|
||||||
|
{ appEnv :: Env,
|
||||||
|
connection :: WS.Connection,
|
||||||
|
clientId :: UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
class HasWSEnv a where
|
||||||
|
getConn :: a -> WS.Connection
|
||||||
|
getClientId :: a -> UUID
|
||||||
|
|
||||||
|
instance HasWSEnv WSEnv where
|
||||||
|
getConn = connection
|
||||||
|
getClientId = clientId
|
||||||
|
|
||||||
|
newtype WSApp env a = WSApp {unWSApp :: env -> IO a}
|
||||||
|
deriving
|
||||||
|
( Functor,
|
||||||
|
Applicative,
|
||||||
|
Monad,
|
||||||
|
MonadReader env,
|
||||||
|
MonadIO,
|
||||||
|
MonadUnliftIO
|
||||||
|
)
|
||||||
|
via ReaderT env IO
|
||||||
|
|
||||||
|
instance HasConnectedClientState WSEnv where
|
||||||
|
getConnectedClientState = connectedClientsState . appEnv
|
||||||
|
|
||||||
|
instance HasRoomsState WSEnv where
|
||||||
|
getRoomsState = roomsState . appEnv
|
||||||
|
|
||||||
|
instance MonadConnectedClientsModify (WSApp WSEnv) where
|
||||||
|
addWSClient = addWSClientGeneric
|
||||||
|
updateWSClient = updateWSClientGeneric
|
||||||
|
removeWSClient = removeWSClientGeneric
|
||||||
|
|
||||||
|
instance MonadConnectedClientsRead (WSApp WSEnv) where
|
||||||
|
getConnctedClients = getConnctedClientsGeneric
|
||||||
|
|
||||||
|
instance MonadRoomDataStateRead (WSApp WSEnv) where
|
||||||
|
getRoomDataState = getRoomState
|
||||||
|
|
||||||
|
instance MonadBroadcast (WSApp WSEnv) where
|
||||||
|
broadCastToClients = broadCastToClientsGeneric
|
Loading…
Reference in New Issue