How to implement PubSub with LiveView?

Hi,

I still working through this article and have now reached the phase, where I want to implement the immediate disconnecting of LiveViews on logout.

Sadly, I seem to misunderstand something or did something, so it doesn’t work. In the end, my problem is, that I think that the LiveViews are connected to a PubSub channel on mount. To do so I call in my mount function:

Jocasta.Endpoint.subscribe(@my_pubsub_topic)

I also added a plain handle_info function to my LiveViews, that should just dump out the message for me.

@impl true
def handle_info(msg, socket) do
  IO.inspect(msg)
  {:noreply, socket}
end

Then I broadcast a message somewhere else.

Jocasta.Endpoint.broadcast_from(
     self(),
     UserAuth.pubsub_topic(),
     "logout_user",
     %{
       user: user
     }
   )

Somehow my message never ends up in my handle_info method or better said, this method is never called.

Being rather new to Phoenix, I have some stupid quesions.

  • Can I find out which subscribers exist for a given topic?
  • Is the assumption correct that subscribe and broadcast belong to the PubSub system?
  • Do I totally misunderstand here something?

I thought I replicated everything the author does in his repo. Sadly, I seem to miss something and started to strip his example to the absolute basics of sending and receiving messages via PubSub.

Any help is appreciated.

Best regards
Oliver

Can I find out which subscribers exist for a given topic?

Registry.lookup(Jocasta.PubSub, topic) the result can contain metadata passed to when calling subscribe (e.g. Phoenix.PubSub.subscribe(Jocasta.PubSub, topic, metadata: %{ip: ip}))

Is the assumption correct that subscribe and broadcast belong to the PubSub system?

It belongs to pubsub_server configured here

config :fset, JocastaWeb.Endpoint,
  ...
  pubsub_server: Jocasta.PubSub,
  live_view: [signing_salt: "abc"]

And that PubSub is usually (or by default, not sure since which phoenix version) started in root supervision tree.

Do I totally misunderstand here something?

Not sure! If that broadcast_from works, you would want to test it in a separate node to see if it actually works. Or use local_broadcast_from.

2 Likes

Thx @50kudos. This helped me a lot.

  • Registry.lookup/2 indeed showed me what I wanted to know.
  • In another post I learned about iex -S mix phx.server. Before that, I started my commands manually in a different iex which is stupid, if you want to interact with another os process.

Now I can debug analyse my problem.

May I ask why you’re using pubsub manually over this:

https://hexdocs.pm/phoenix_live_view/0.14.8/security-model.html#disconnecting-all-instances-of-a-given-live-user

2 Likes

Because I didn’t knew about this. This is really helpful. Thanks for pointing this out to me.

1 Like

I have been following this same article and had some concerns about the proposed use of PubSub. The link posted above by @LostKobrakai is a much better approach.

I want to mention this:
I used phx_gen_auth in my project and that generated this line in the log_in_user function:

conn
# ...
|> put_session(:live_socket_id, "users_sessions:#{Base.url_encode64(token)}")

So for anyone else like me, you can keep this line and log users out like this:

  @doc """
  Logs a user out of any and all sessions
  """
  def force_logout_user(%User{id: user_id} = user) do
    user_tokens = UserToken
    |> where(user_id: ^user_id)
    |> Repo.all()

    Repo.delete_all(UserToken.user_and_contexts_query(user, :all))

    user_tokens
    |> Enum.each(fn %UserToken{token: token} ->
      MyAppWeb.Endpoint.broadcast(
        "users_sessions:#{Base.url_encode64(token)}",
        "disconnect",
        %{}
      )
    end)
  end

Just make sure you change mount functions in any live views you have that might assume a current_user on the assigns.

2 Likes

Hey, thanks a bunch for posting your solution to this. I think I too am following the same article, popular one I guess haha. I am wondering if your approach will trigger the page to “re-render” or the mount function to run again.

From what I was reading it should. However, when I call the force_logout_user function from an iex -S mix session the page in my browser does not automatically refresh and the mount function does not trigger like I expect it to. I do not see any errors in the output from the Phoenix Server. Any insight would be appreciated. Thanks :slight_smile:

When I call force_logout_user the logged in user’s browser navigates to the login page.

Are you definitely putting the live socket ID on the sesssion? I have something like this in my log_in function:

    |> put_session(:live_socket_id, "users_sessions:#{Base.url_encode64(token)}")

and that token is stored in the database.

Take a look at the Network tab in your devtools to investigate the problem.