jitsi-roomsv2/backend/src/WebSocket.hs

234 lines
5.2 KiB
Haskell
Raw Normal View History

2023-02-07 20:30:25 +01:00
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FlexibleContexts #-}
2023-02-18 16:57:20 +01:00
{-# LANGUAGE FlexibleInstances #-}
2023-01-27 18:34:28 +01:00
{-# LANGUAGE UndecidableInstances #-}
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
2023-02-18 16:57:20 +01:00
import Data.Aeson
2023-02-07 20:30:25 +01:00
import Data.UUID (UUID)
import Data.UUID.V4 (nextRandom)
import Network.WebSockets qualified as WS
import Types.AppTypes
( Env (..),
HasConnectedClientState (getConnectedClientState),
)
2023-02-18 16:57:20 +01:00
import Types.ConnectionState
( Client (..),
ConnectedClients,
)
2023-02-07 20:30:25 +01:00
import Types.RoomsState (HasRoomsState (..))
2023-02-18 16:57:20 +01:00
import Types.WebSocketMessages.WebSocketMessages
( SetClientInfo (displayName),
WebSocketMessage (..),
)
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'
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"
modifyMVar_ state $ \s ->
let s' = updateClient clientId (\c -> c {joinedRoom = True}) s
in return s'
2023-02-18 16:57:20 +01:00
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'
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
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
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
) =>
2023-02-18 16:57:20 +01:00
SetClientInfo ->
2023-01-27 18:34:28 +01:00
m Client
2023-02-18 16:57:20 +01:00
newClient clientInfo = do
2023-01-27 18:34:28 +01:00
env <- ask
2023-02-18 16:57:20 +01:00
return $ Client {uuid = getClientId env, name = displayName clientInfo, 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
2023-02-18 16:57:20 +01:00
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
2023-01-27 18:34:28 +01:00
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,
2023-02-18 16:57:20 +01:00
MonadUnliftIO m,
MonadWebSocket m
2023-01-27 18:34:28 +01:00
) =>
m ()
wsApp = do
2023-02-18 16:57:20 +01:00
msg <- getTypedWSMessage
print msg
2023-01-27 18:34:28 +01:00
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
2023-02-18 16:57:20 +01:00
handleWSAction
2023-02-07 20:30:25 +01:00
broadcastUserData
2023-02-18 16:57:20 +01:00
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
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'
2023-02-18 16:57:20 +01:00
sendMessage ::
( HasWSEnv env,
MonadIO m,
MonadReader env m
) =>
Text ->
m ()
sendMessage msg = do
conn' <- getConn <$> ask
liftIO $ WS.sendTextData conn' msg