Pure WS monad
This commit is contained in:
		
							parent
							
								
									5d3dced6f7
								
							
						
					
					
						commit
						865b69e799
					
				
					 12 changed files with 346 additions and 221 deletions
				
			
		|  | @ -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 | ||||||
|  |  | ||||||
							
								
								
									
										14
									
								
								backend/src/State/RoomDataState.hs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								backend/src/State/RoomDataState.hs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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}) | ||||||
|  |  | ||||||
							
								
								
									
										30
									
								
								backend/src/WebSocket/Messages.hs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								backend/src/WebSocket/Messages.hs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||||
							
								
								
									
										64
									
								
								backend/src/WebSocket/MonadWebSocketSession.hs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								backend/src/WebSocket/MonadWebSocketSession.hs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||||
							
								
								
									
										39
									
								
								backend/src/WebSocket/Server.hs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								backend/src/WebSocket/Server.hs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||||
|  |     ) | ||||||
							
								
								
									
										74
									
								
								backend/src/WebSocket/WSReaderTApp.hs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								backend/src/WebSocket/WSReaderTApp.hs
									
										
									
									
									
										Normal file
									
								
							|  | @ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue