2023-02-07 20:30:25 +01:00
|
|
|
{-# LANGUAGE DerivingVia #-}
|
|
|
|
{-# LANGUAGE FlexibleContexts #-}
|
2023-01-27 18:34:28 +01:00
|
|
|
{-# LANGUAGE UndecidableInstances #-}
|
2023-01-28 19:08:33 +01:00
|
|
|
|
2023-02-07 20:30:25 +01:00
|
|
|
module WebSocket (runWebSocketServer) where
|
2023-01-27 18:34:28 +01:00
|
|
|
|
2023-02-07 20:30:25 +01:00
|
|
|
import BroadcastUserData (broadcastUserData)
|
|
|
|
import ClassyPrelude
|
|
|
|
import Data.UUID (UUID)
|
|
|
|
import Data.UUID.V4 (nextRandom)
|
|
|
|
import Network.WebSockets qualified as WS
|
|
|
|
import Types.AppTypes
|
|
|
|
( Env (..),
|
|
|
|
HasConnectedClientState (getConnectedClientState),
|
|
|
|
)
|
|
|
|
import Types.ConnectionState (Client (..), ConnectedClients)
|
|
|
|
import Types.RoomsState (HasRoomsState (..))
|
2023-01-27 19:53:34 +01:00
|
|
|
|
|
|
|
data WSEnv = WSEnv
|
2023-02-07 20:30:25 +01:00
|
|
|
{ appEnv :: Env,
|
2023-01-27 19:53:34 +01:00
|
|
|
connection :: WS.Connection,
|
2023-02-07 20:30:25 +01:00
|
|
|
clientId :: UUID
|
2023-01-27 19:53:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
instance HasConnectedClientState WSEnv where
|
|
|
|
getConnectedClientState = connectedClientsState . appEnv
|
|
|
|
|
2023-02-07 20:30:25 +01:00
|
|
|
instance HasRoomsState WSEnv where
|
|
|
|
getRoomsState = roomsState . appEnv
|
|
|
|
|
2023-01-27 19:53:34 +01:00
|
|
|
class HasWSEnv a where
|
|
|
|
getConn :: a -> WS.Connection
|
|
|
|
getClientId :: a -> UUID
|
|
|
|
|
|
|
|
instance HasWSEnv WSEnv where
|
|
|
|
getConn = connection
|
|
|
|
getClientId = clientId
|
|
|
|
|
|
|
|
addWSClient ::
|
|
|
|
( HasConnectedClientState env,
|
|
|
|
MonadReader env m,
|
|
|
|
MonadUnliftIO m
|
|
|
|
) =>
|
|
|
|
Client ->
|
|
|
|
m ()
|
|
|
|
addWSClient client = do
|
|
|
|
state <- getConnectedClientState <$> ask
|
|
|
|
modifyMVar_ state $ \s -> do
|
|
|
|
let s' = addClient client s
|
|
|
|
return s'
|
2023-01-15 18:26:41 +01:00
|
|
|
|
2023-01-27 18:34:28 +01:00
|
|
|
addClient :: Client -> ConnectedClients -> ConnectedClients
|
2023-01-15 18:26:41 +01:00
|
|
|
addClient client clients = client : clients
|
|
|
|
|
2023-01-27 19:53:34 +01:00
|
|
|
disconnectWsClient ::
|
|
|
|
( HasWSEnv env,
|
|
|
|
HasConnectedClientState env,
|
|
|
|
MonadReader env m,
|
|
|
|
MonadUnliftIO m
|
|
|
|
) =>
|
|
|
|
m ()
|
|
|
|
disconnectWsClient = do
|
|
|
|
clientId <- getClientId <$> ask
|
|
|
|
state <- getConnectedClientState <$> ask
|
|
|
|
modifyMVar_ state $ \s ->
|
|
|
|
let s' = removeClient clientId s
|
|
|
|
in return s'
|
|
|
|
|
2023-01-28 19:08:33 +01:00
|
|
|
joinRoom ::
|
|
|
|
( HasWSEnv env,
|
|
|
|
HasConnectedClientState env,
|
|
|
|
MonadReader env m,
|
|
|
|
MonadUnliftIO m
|
|
|
|
) =>
|
|
|
|
m ()
|
|
|
|
joinRoom = do
|
|
|
|
clientId <- getClientId <$> ask
|
|
|
|
state <- getConnectedClientState <$> ask
|
2023-02-07 20:30:25 +01:00
|
|
|
liftIO $ putStrLn "joinedRoom"
|
2023-01-28 19:08:33 +01:00
|
|
|
modifyMVar_ state $ \s ->
|
|
|
|
let s' = updateClient clientId (\c -> c {joinedRoom = True}) s
|
|
|
|
in return s'
|
|
|
|
|
2023-01-27 18:34:28 +01:00
|
|
|
removeClient :: UUID -> ConnectedClients -> ConnectedClients
|
|
|
|
removeClient toRemove = filter ((/= toRemove) . uuid)
|
2023-01-15 18:26:41 +01:00
|
|
|
|
2023-01-28 19:08:33 +01:00
|
|
|
updateClient :: UUID -> (Client -> Client) -> ConnectedClients -> ConnectedClients
|
|
|
|
updateClient toUpdate f allClients = do
|
|
|
|
client <- allClients
|
2023-02-07 20:30:25 +01:00
|
|
|
if toUpdate == uuid client
|
|
|
|
then return $ f client
|
2023-01-28 19:08:33 +01:00
|
|
|
else return client
|
|
|
|
|
2023-01-27 18:34:28 +01:00
|
|
|
runWebSocketServer ::
|
|
|
|
( MonadIO m,
|
|
|
|
MonadReader Env m
|
|
|
|
) =>
|
|
|
|
m ()
|
|
|
|
runWebSocketServer = do
|
|
|
|
putStrLn "Websocket up at 127.0.0.1:9160"
|
2023-01-27 19:53:34 +01:00
|
|
|
wsApp' <- runWSApp
|
|
|
|
liftIO $ WS.runServer "127.0.0.1" 9160 wsApp'
|
2023-01-27 18:34:28 +01:00
|
|
|
|
|
|
|
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}
|
2023-02-07 20:30:25 +01:00
|
|
|
WS.withPingThread conn 30 (return ()) $ unWSApp wsApp wsEnv
|
2023-01-27 18:34:28 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
newClient ::
|
|
|
|
( MonadIO m,
|
|
|
|
HasWSEnv env,
|
|
|
|
MonadReader env m
|
|
|
|
) =>
|
|
|
|
Text ->
|
|
|
|
m Client
|
|
|
|
newClient name = do
|
|
|
|
env <- ask
|
2023-01-28 19:08:33 +01:00
|
|
|
return $ Client {uuid = getClientId env, name = name, conn = getConn env, joinedRoom = False}
|
2023-01-27 18:34:28 +01:00
|
|
|
|
|
|
|
newtype WSApp env a = WSApp {unWSApp :: env -> IO a}
|
|
|
|
deriving
|
|
|
|
( Functor,
|
|
|
|
Applicative,
|
|
|
|
Monad,
|
|
|
|
MonadReader env,
|
|
|
|
MonadIO,
|
|
|
|
MonadUnliftIO
|
|
|
|
)
|
|
|
|
via ReaderT env IO
|
|
|
|
|
|
|
|
wsApp ::
|
|
|
|
( HasWSEnv env,
|
|
|
|
HasConnectedClientState env,
|
2023-02-07 20:30:25 +01:00
|
|
|
HasRoomsState env,
|
2023-01-27 18:34:28 +01:00
|
|
|
MonadReader env m,
|
|
|
|
MonadUnliftIO m
|
|
|
|
) =>
|
|
|
|
m ()
|
|
|
|
wsApp = do
|
|
|
|
msg <- getMessage
|
|
|
|
putStrLn msg
|
|
|
|
client <- newClient msg
|
2023-01-27 19:53:34 +01:00
|
|
|
addWSClient client
|
2023-02-07 20:30:25 +01:00
|
|
|
broadcastUserData
|
|
|
|
flip finally disconnectWsClient $ forever $ do
|
|
|
|
currentMsg <- getMessage
|
|
|
|
joinRoom
|
|
|
|
broadcastUserData
|
|
|
|
putStrLn currentMsg
|
2023-01-27 18:34:28 +01:00
|
|
|
|
|
|
|
getMessage ::
|
|
|
|
( HasWSEnv env,
|
|
|
|
MonadIO m,
|
|
|
|
MonadReader env m
|
|
|
|
) =>
|
|
|
|
m Text
|
|
|
|
getMessage = do
|
|
|
|
conn' <- getConn <$> ask
|
|
|
|
liftIO $ WS.receiveData conn'
|