Suspending execution in HalogenM

halogen

#1

I’m working on implementing authentication in a Halogen app and I’m trying to find a neat way to require authentication in a component.

A common pattern seems to be to use something like guardSession from the real world halogen project, which looks like this:

-- | Several components verify that a current user exists and, if there is none in state, log the 
-- | user out and redirect to the home page. This way, an inadvertent route to the settings page, 
-- | for example, is protected at initialization.
guardSession
  :: forall m r
   . MonadEffect m
  => MonadAsk { currentUser :: Ref (Maybe Profile) | r } m
  => Navigate m
  => m (Maybe Profile)
guardSession = do 
  asks _.currentUser >>= (Ref.read >>> liftEffect) >>= case _ of
    Nothing -> logout *> pure Nothing 
    Just profile -> pure (Just profile)

This would be used something like this:

eval = case _ of
    Initialize a -> do
      void $ H.fork $ eval $ LoadTags a
      guardSession >>= case _ of
        Nothing -> do 
          void $ H.fork $ eval $ LoadArticles noArticleParams a
        profile -> do
          void $ H.fork $ eval $ LoadFeed { limit: Just 20, offset: Nothing } a
          H.modify_ _ { currentUser = profile, tab = Feed }
      pure a

This works well enough but I feel like there’s a different way I would like to express things. I would like to require that the application give me an auth token. Something like this:

eval = case _ of
    Initialize a -> do
      token <- fetchAuthToken
      ... -- do things with auth token

What I would want to happen here is that the application would suspend execution at this point, show a login form, perform authentication, then return to the same point with the value of the token.

That feels a lot like a coroutine, so it feels like I should be looking at writing a natural transformation from my application query language (which is built using purescript-run, although I’m not wedded to the extensible effects style) to Aff that uses coroutines. But I can’t work out what that might look like.

Has anyone any experience with doing something like this? Any tips? I appreciate that it’s not a particularly well defined question.