WS: `send_message` function. Will soon serve for sending keepalive messages.

beta
Philippe Pittoli 2024-02-20 04:57:41 +01:00
parent bcc76c8378
commit b6b6a6be77
2 changed files with 41 additions and 34 deletions

View File

@ -7,8 +7,6 @@
-- | `App.Container` stores the state of different components (domain list and zone interface) -- | `App.Container` stores the state of different components (domain list and zone interface)
-- | to avoid useless requests to `dnsmanagerd`. -- | to avoid useless requests to `dnsmanagerd`.
-- | -- |
-- | TODO: store forms in session storage?
-- |
-- | `App.Container` detects when a page has been reloaded and: -- | `App.Container` detects when a page has been reloaded and:
-- | 1. authenticate the user to `dnsmanagerd` via a stored token in session storage. -- | 1. authenticate the user to `dnsmanagerd` via a stored token in session storage.
-- | The authentication to `dnsmanagerd` automatically provides own domains and accepted domains (such as `netlib.re`). -- | The authentication to `dnsmanagerd` automatically provides own domains and accepted domains (such as `netlib.re`).
@ -37,12 +35,10 @@
-- | - mail verification: TODO -- | - mail verification: TODO
-- | - domain list: domain (`label`) is insufficient. -- | - domain list: domain (`label`) is insufficient.
-- | -- |
-- | TODO: when reading a RR `name`, always make it an FQDN by adding `<user-domain>.netlib.re.". -- | TODO: when reading a RR `name`, always make it an FQDN by adding `<user-domain>.netlib.re.`.
-- | -- |
-- | TODO: remove the FQDN when showing RR names. -- | TODO: remove the FQDN when showing RR names.
-- | -- |
-- | TODO: authd administrative page
-- |
-- | TODO: application-level heartbeat to avoid disconnections. -- | TODO: application-level heartbeat to avoid disconnections.
-- | -- |
-- | Untested features: -- | Untested features:

View File

@ -2,8 +2,10 @@
-- | This includes telling when a connection is established or closed, and notify a message has been received. -- | This includes telling when a connection is established or closed, and notify a message has been received.
module App.WS where module App.WS where
import Prelude (Unit, bind, discard, pure, show, void, when, ($), (&&), (<$>), (<>), (>>=), (>=>), (<<<), map) import Prelude (Unit, bind, discard, pure, show, void, when
, ($), (&&), (<$>), (<>), (>>=), (>=>), (<<<), map, (=<<), (+))
import Control.Monad.Rec.Class (forever)
import Control.Monad.Except (runExcept) import Control.Monad.Except (runExcept)
import Data.Array as A import Data.Array as A
import Data.ArrayBuffer.Types (ArrayBuffer) import Data.ArrayBuffer.Types (ArrayBuffer)
@ -12,6 +14,8 @@ import Data.Either (Either(..))
import Data.Maybe (Maybe(..), isJust, isNothing) import Data.Maybe (Maybe(..), isJust, isNothing)
import Data.String as String import Data.String as String
import Data.Tuple (Tuple(..)) import Data.Tuple (Tuple(..))
import Effect.Aff as Aff
import Effect.Aff (Milliseconds(..))
import Effect.Aff.Class (class MonadAff) import Effect.Aff.Class (class MonadAff)
import Effect (Effect) import Effect (Effect)
import Foreign as F import Foreign as F
@ -30,6 +34,8 @@ import Web.Socket.WebSocket as WS
import App.LogMessage import App.LogMessage
keepalive = 30000.0 :: Number
-- Input is the WS url. -- Input is the WS url.
type Input = String type Input = String
@ -47,6 +53,15 @@ data Query a = ToSend ArrayBuffer a
type Slot = H.Slot Query Output type Slot = H.Slot Query Output
-- | `timer` triggers a `Tick` action every `keepalive` seconds.
timer :: forall m a. MonadAff m => a -> m (HS.Emitter a)
timer val = do
{ emitter, listener } <- H.liftEffect HS.create
_ <- H.liftAff $ Aff.forkAff $ forever do
Aff.delay $ Milliseconds keepalive
H.liftEffect $ HS.notify listener val
pure emitter
data Action data Action
-- | `Initialize` opens the connection (URL is received as an `input` of this component). -- | `Initialize` opens the connection (URL is received as an `input` of this component).
= Initialize = Initialize
@ -64,6 +79,8 @@ data Action
-- | `Finalize` is the action performed once the component is destroyed, ending the connection. -- | `Finalize` is the action performed once the component is destroyed, ending the connection.
| Finalize | Finalize
| Tick
-- | Every received websocket message and notification is handled in `HandleWebSocket`. -- | Every received websocket message and notification is handled in `HandleWebSocket`.
| HandleWebSocket (WebSocketEvent WebSocketMessageType) | HandleWebSocket (WebSocketEvent WebSocketMessageType)
@ -77,7 +94,7 @@ type WSInfo
} }
-- | The state of this component only is composed of the websocket. -- | The state of this component only is composed of the websocket.
type State = { wsInfo :: WSInfo } type State = { wsInfo :: WSInfo, seconds :: Number }
component :: forall m. MonadAff m => H.Component Query Input Output m component :: forall m. MonadAff m => H.Component Query Input Output m
component = component =
@ -98,13 +115,15 @@ initialState url =
, connection: Nothing , connection: Nothing
, reconnect: false , reconnect: false
} }
, seconds: 0.0
} }
-- | The component shows a string when the connection is established, or a button when the connection has closed. -- | The component shows a string when the connection is established, or a button when the connection has closed.
render :: forall m. State -> H.ComponentHTML Action () m render :: forall m. State -> H.ComponentHTML Action () m
render { wsInfo } render { wsInfo, seconds }
= HH.div_ = HH.div_
[ renderReconnectButton (isNothing wsInfo.connection && wsInfo.reconnect) [ renderReconnectButton (isNothing wsInfo.connection && wsInfo.reconnect)
, HH.text ("You have been here for " <> show seconds <> " seconds")
] ]
where where
@ -137,9 +156,16 @@ handleAction :: forall m. MonadAff m => Action -> H.HalogenM State Action () Out
handleAction action = do handleAction action = do
{ wsInfo } <- H.get { wsInfo } <- H.get
case action of case action of
Initialize -> Initialize -> do
_ <- H.subscribe =<< timer Tick
handleAction ConnectWebSocket handleAction ConnectWebSocket
Tick -> do
H.modify_ \state -> state { seconds = state.seconds + keepalive }
-- TODO: create a message only for applicative keepalive.
-- message
-- send_message message
Finalize -> do Finalize -> do
-- H.raise $ Log $ SystemLog $ "Closing websocket for '" <> wsInfo.url <> "'" -- H.raise $ Log $ SystemLog $ "Closing websocket for '" <> wsInfo.url <> "'"
case wsInfo.connection of case wsInfo.connection of
@ -200,36 +226,21 @@ handleAction action = do
handleQuery :: forall a m. MonadAff m => Query a -> H.HalogenM State Action () Output m (Maybe a) handleQuery :: forall a m. MonadAff m => Query a -> H.HalogenM State Action () Output m (Maybe a)
handleQuery = case _ of handleQuery = case _ of
ToSend message a -> do ToSend message a -> send_message message
send_message :: forall m. MonadAff m => ArrayBuffer -> H.HalogenM State Action () Output m Unit
send_message message =
{ wsInfo } <- H.get { wsInfo } <- H.get
case wsInfo.connection of case wsInfo.connection of
Nothing -> do Nothing -> H.raise $ Log $ UnableToSend "Not connected to server."
H.raise $ Log $ UnableToSend "Not connected to server."
pure Nothing
Just webSocket -> do Just webSocket -> do
H.liftEffect (WS.readyState webSocket) >>= case _ of H.liftEffect (WS.readyState webSocket) >>= case _ of
Connecting -> do Connecting -> H.raise $ Log $ UnableToSend "Still connecting to server."
H.raise $ Log $ UnableToSend "Still connecting to server." Closing -> H.raise $ Log $ UnableToSend "Connection to server is closing."
pure Nothing Closed -> do
Closing -> do
H.raise $ Log $ UnableToSend "Connection to server is closing."
pure Nothing
Closed -> do
H.raise $ Log $ UnableToSend "Connection to server has been closed." H.raise $ Log $ UnableToSend "Connection to server has been closed."
maybeCurrentConnection <- H.gets _.wsInfo.connection H.modify_ _ { wsInfo { connection = Nothing, reconnect = true } }
when (isJust maybeCurrentConnection) do Open -> H.liftEffect $ sendArrayBuffer webSocket message
H.modify_ _ { wsInfo { connection = Nothing, reconnect = true } }
pure Nothing
Open -> do
H.liftEffect $ do
sendArrayBuffer webSocket message
pure (Just a)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- WebSocket mess. -- WebSocket mess.
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------