Question: Halogen and Websockets

Hi! I’ve been playing around with Purescript and Halogen and I stumbled upon a problem I can’t seem to figure out how to solve.

In all the Websocket examples I’ve seen the connection is created in main, however in my case I need to be able to setup the connection after a user logs in as this determines the url for the connection.

I’m currently using the purescript-web-socket package, the websocket-simple looks somewhat… well… simpler though.

I had look at: https://github.com/nathanic/purescript-simple-chat-client which at first seemed like it would be helpful however that example seems to be a bit dated.

Thanks!

I’m assuming some things about your component architechture. Namely I’m assuming you’ve hoist ed your component tree into the Aff monad or some monad that has the capabilities of Aff. If not, you will need to do that. check out Real World Halogen to see examples on how to do this (I believe it uses ReaderT) https://github.com/thomashoneyman/purescript-halogen-realworld

So your top-level application component could have some state, a Maybe WebSocket for example, and the websocket can be created only following a successful log in. if your architecture is such that you have a parent component which the user sees only after they’ve logged in, then it is probably safest that the Maybe Websocket state live in that parent component, and that an action is launched from the init function which creates the socket and sets it into state.

if you wish to give child components access to the socket directly, you can pass the socket down as input or messages, Alternatively your parent component could serve as a hub for all messages which need to be passed to/from the WebSocket

@erikbackman Setting up a websocket connection happens in Effect, which means that you can do it anywhere you can run effectful code. That’s certainly the case in your main function, but it’s also the case in your Halogen components (so long as the component can be run in Aff or Effect).

We can run with @Benjmhart’s suggestion and say that your component receives the connection URL as input. This would be a fairly common type to represent that:

type Input = { url :: String }

component :: H.Component HH.HTML query Input output Aff

-- or
component :: MonadAff m => H.Component HH.HTML query Input output m

This component can, when it initializes, start the websocket connection. For example, you might do something like this:

data Action = Initialize

component = ...
  { initializer = Just Initialize
  , handleAction = handleAction
  }
  where
  handleAction = case _ of
    Initialize -> do
      { url } <- H.get
      H.liftEffect do
        -- whatever code you want to run in Effect, like open 
        -- a websocket at the provided `url`, just like you 
        -- would write in your `main` function

I haven’t done any work with websockets myself so I’m not sure exactly what you need to do here. Even so, this is how you might pass the URL the user needs to connect to down to a component which actually starts up that connection. You may need to add a finalizer which closes the websocket when the component unmounts.


As far as further examples go, these may be useful to you (they use purescript-web-socket):

I often look for example projects using libraries that I’m also using by searching the library name in GitHub and filtering by language:purescript. For example, to find projects using websocket-simple, you can search purescript-websocket-simple language:purescript.


As a side note, the purescript-web organization supplies types and low-level implementations for various specs, but the libraries aren’t much fun to use. If websocket-simple can do what you need there’s no reason to prefer the purescript-web libraries.

1 Like

I feel so silly now, not sure why I struggled to realise this. Thanks @thomashoneyman and @Benjmhart!

2 Likes

Hi @thomashoneyman what would, in your opinion, be the advantage of managing the websocket in the component’s initialize / finalize functions as opposed to the “official” halogen example which uses subscriptions and queries to the component ?

By the way, the books says:

query allows us to send queries into the component, using its query algebra ( f ).
This is useful for things like routing, or driving an app from an external source -
WebSockets, for example.

But it doesn’t specify this further in the context as far as i can see.

1 Like

@flip101 I don’t think there’s an advantage one way or another.

It doesn’t really matter whether you choose to set up the websocket in the initializer / finalizer functions of a component, or in your application’s main function that runs the root component of your app. I think that decision is more about where websockets are used in your app – if the whole application is being driven by a websocket, then perhaps set it up in main; if some sub-part of the application needs a websocket, set it up in in that component instead.

The official example sets everything up in main. It runs the root component, which returns a record of the type io :: HalogenIO. This record contains functions that let you subscribe to (and handle) output messages from the root component (io.subscribe) and query the root component (io.query).

But these two functions are just the ordinary component communication you’re already used to. io.subscribe is the same thing as handling messages from a child component, and io.query is the same thing as running H.query to query a child component.

HalogenIO is only used because there is no parent component for the root component. If you need to send a query to the root component or handle an output message from it, then in your app’s main function you will use the functions returned by runUI (the HalogenIO record).

So there isn’t anything websocket-specific about io.subscribe or io.query.

2 Likes