Phoenix.PubSub.subscribe using a "string" or string?

Hi all,

I can’t get pubsub to work unless my topic is like this "topic" I want my variable topic, that doesn’t work:

defmodule SentrypeerWeb.CustomerSettingsLive.Overview do

...

  @impl true
  def handle_params(%{"client_id" => client_id}, _url, socket) do
    if connected?(socket), do: SentrypeerEvents.subscribe(client_id)

...

  @impl true
  def handle_info({phone_number, client_id}, socket) do
    Logger.debug("Client #{client_id} has just searched for a phone number.")
    {:noreply, assign(socket, :phone_number, phone_number)}
  end
defmodule Sentrypeer.SentrypeerEvents do

....

  def subscribe(client_id) do
    Logger.debug("Subscribing to topic: #{client_id}")
    Phoenix.PubSub.subscribe(Sentrypeer.PubSub, "client_id")
  end

  defp broadcast({:error, _reason} = error, _client_id), do: error

  defp broadcast({:ok, phone_number}, client_id) do
    Phoenix.PubSub.broadcast(Sentrypeer.PubSub, "client_id", {phone_number, client_id})
    {:ok, phone_number}
  end

You can see above I’ve hardcoded client_id, which works. What am I missing?

I’ve tried "#{client_id}"

It’s hard to say for sure because the connections between SentrypeerEvents.broadcast and the rest aren’t shown, but my guess is that the client_id variable isn’t always a binary. Check where broadcast is being called.

FWIW, consider using a prefixed topic like clients:#{client_id} since PubSub is a single global namespace.

1 Like

Sorry, missed showing the broadcast inside the above module:

  def check_phone_number_sentrypeer_event?(phone_number, client_id) do
    changeset =
      SentrypeerPhoneNumber.changeset(%SentrypeerPhoneNumber{}, %{phone_number: phone_number})

    if changeset.valid? do
      query =
        from e in SentrypeerEvent,
          where: e.called_number == ^changeset.changes.phone_number

      # and
      #  e.client_id == ^client_id

      broadcast({:ok, phone_number}, client_id)
      Repo.exists?(query)
    else
      false
    end
  end

which is called from defmodule SentrypeerWeb.SentrypeerEventController:

  def check_phone_number(conn, %{"phone_number" => phone_number}) do
    case SentrypeerEvents.check_phone_number_sentrypeer_event?(
           phone_number,
           conn.assigns.client_id
         ) do
      true ->
        conn
        |> put_status(:found)
        |> json(%{message: "Phone Number found."})

      false ->
        conn
        |> put_status(:not_found)
        |> json(%{message: "Phone Number not found."})
    end
  end

so I guess the questions is, what is client_id in my assigns.

Throughout all this, isn’t client_id always a String type?

Are "client_id" and client_id variable the same Type?

If I do like you suggest and go for prefix like `“client_id:#{client_id}”, this should force consistent types?

Should I expect Phoenix.PubSub.subscribe and Phoenix.PubSub.broadcast to complain?

Thanks.

So I’ve tried `“client_id:#{client_id}” with no luck.

I’ve done this and they are all the same:

  @impl true
  def handle_params(%{"client_id" => client_id}, _url, socket) do
    Logger.debug(IEx.Info.info(client_id))
    Logger.debug(IEx.Info.info("client_id"))
    Logger.debug(IEx.Info.info("client_id:#{client_id}"))
    if connected?(socket), do: SentrypeerEvents.subscribe(client_id)

e.g.:

[debug] [{"Data type", "BitString"}, {"Byte size", 32}, {"Description", "This is a string: a UTF-8 encoded binary. It's printed surrounded by\n\"double quotes\" because all UTF-8 encoded code points in it are printable.\n"}, {"Raw representation", "<<105, 90, 110, 83, 53, 102, 107, 120, 57, 71, 114, 80, 112, 102, 100, 122, 83, 117, 65, 119, 53, 73, 106, 67, 113, 110, 76, 81, 87, 49, 80, 48>>"}, {"Reference modules", "String, :binary"}]
[debug] [{"Data type", "BitString"}, {"Byte size", 9}, {"Description", "This is a string: a UTF-8 encoded binary. It's printed surrounded by\n\"double quotes\" because all UTF-8 encoded code points in it are printable.\n"}, {"Raw representation", "<<99, 108, 105, 101, 110, 116, 95, 105, 100>>"}, {"Reference modules", "String, :binary"}]
[debug] [{"Data type", "BitString"}, {"Byte size", 42}, {"Description", "This is a string: a UTF-8 encoded binary. It's printed surrounded by\n\"double quotes\" because all UTF-8 encoded code points in it are printable.\n"}, {"Raw representation", "<<99, 108, 105, 101, 110, 116, 95, 105, 100, 58, 105, 90, 110, 83, 53, 102, 107, 120, 57, 71, 114, 80, 112, 102, 100, 122, 83, 117, 65, 119, 53, 73, 106, 67, 113, 110, 76, 81, 87, 49, 80, 48>>"}, {"Reference modules", "String, :binary"}]

and added the same debug into the functions I’ve already shown:

def subscribe(client_id) do
    Logger.debug(IEx.Info.info(client_id))
    Logger.debug("Subscribing to topic 'client_id:#{client_id}'")
    Phoenix.PubSub.subscribe(Sentrypeer.PubSub, "client_id:#{client_id}")
  end

  defp broadcast({:error, _reason} = error, _client_id), do: error

  defp broadcast({:ok, phone_number}, client_id) do
    Logger.debug(IEx.Info.info(client_id))

    Phoenix.PubSub.broadcast(
      Sentrypeer.PubSub,
      "client_id:#{client_id}",
      {phone_number, client_id}
    )

    {:ok, phone_number}
  end

which gives:

[debug] [{"Data type", "BitString"}, {"Byte size", 32}, {"Description", "This is a string: a UTF-8 encoded binary. It's printed surrounded by\n\"double quotes\" because all UTF-8 encoded code points in it are printable.\n"}, {"Raw representation", "<<105, 90, 110, 83, 53, 102, 107, 120, 57, 71, 114, 80, 112, 102, 100, 122, 83, 117, 65, 119, 53, 73, 106, 67, 113, 110, 76, 81, 87, 49, 80, 48>>"}, {"Reference modules", "String, :binary"}]
[debug] [{"Data type", "BitString"}, {"Byte size", 32}, {"Description", "This is a string: a UTF-8 encoded binary. It's printed surrounded by\n\"double quotes\" because all UTF-8 encoded code points in it are printable.\n"}, {"Raw representation", "<<105, 90, 110, 83, 53, 102, 107, 120, 57, 71, 114, 80, 112, 102, 100, 122, 83, 117, 65, 119, 53, 73, 106, 67, 113, 110, 76, 81, 87, 49, 80, 48>>"}, {"Reference modules", "String, :binary"}]

so now I’m TOTALLY confused :slight_smile:

Thanks for reading!

As soon as I switch to "client_id" as the topic, all is working. But they are the SAME! No? :slight_smile:

    Phoenix.PubSub.subscribe(Sentrypeer.PubSub, "client_id")

and

    Phoenix.PubSub.broadcast(
      Sentrypeer.PubSub,
      "client_id",
      {phone_number, client_id}
    )

I’ve read many examples where the topic is like "client_id:#{client_id}".

What basic thing am I not getting?

Thanks.

My original concern was that client_id might be an integer sometimes (if it was a database ID, for instance) but the followup post shows that isn’t relevant since it’s always an alphanumeric binary.

Passing the binary literal "client_id" to subscribe subscribes to a channel literally named client_id.

Passing client_id to subscribe when client_id is bound to the binary "iZnS5fkx9GrPpfdzSuAw5IjCqnLQW1P0" subscribes to a channel named iZnS5fkx9GrPpfdzSuAw5IjCqnLQW1P0.

Switching to the binary literal makes it “work” because it means that every broadcast goes to every subscriber because they’re all listening on the same topic - this looks correct if you’re the only user logged in, but fails if there are two users interacting with different client_ids.

1 Like

I’ve also read here phoenix_pubsub/pubsub.ex at main · phoenixframework/phoenix_pubsub · GitHub and there is a test with is_binary, so it’s not that.

This client_id is specific to the logged in user, as it belongs to them. The subscribe will only be to that topic and the broadcast is done inside an authenticated RESTful API query specific to the client_id from their JWT claims.

I need to go for a walk as I can’t see the difference between "client_id" and just the value bound to client_id, which is ALWAYS iZnS5fkx9GrPpfdzSuAw5IjCqnLQW1P0 in this case.

OMG.

I hadn’t inspected the client_id :slight_smile:

It was the wrong one @al2o3cr !!!

[debug] Broadcasting to: 'client_id:6llN0h7ncCVQdfPMZz20hDtSGwHmdZ3N'

and I thought it was 'client_id:iZnS5fkx9GrPpfdzSuAw5IjCqnLQW1P0.

You were right :smiley:

BAASSIICCs! All this time wasted…but not really. Learned a lot! Especially:

Logger.debug(IEx.Info.info(client_id))

:joy_cat: We’ve all been there! I find this video perfectly sums up the experience:

4 Likes

I laugh hard at the end of the video :rofl: :rofl: :rofl:

1 Like