absinthe-graphql / absinthe_phoenix

MIT License
308 stars 82 forks source link

Duplicate subscriptions sent when same user is logged in with different browsers #83

Open dlobo opened 3 years ago

dlobo commented 3 years ago

We are using subscriptions a fair bit to communicate the state of messages between client and server. At times, the same user will log in via two different browsers.

When this happens, we have the same subscription sent twice to BOTH browsers. They also have the same subscription id.

We have the user object in the absinthe context. We also are using publish_data to publish the subscriptions from the code base (and not via the schema)

coladarci commented 3 years ago

Isn't this "on you" ? I.e you have to "scope" the join w/ a session ID you create; that's what we are doing, at least...

dlobo commented 3 years ago

Isn't this "on you" ? I.e you have to "scope" the join w/ a session ID you create; that's what we are doing, at least...

can you elaborate on what you mean by "scope" the join. Example code would be great. we can definitely do it from different browsers (since its different auth tokens). not sure if we can do it with different tabs from the same browser

coladarci commented 3 years ago

Yeah, in your connect function you can authenticate and also store anything else you want to identify the socket. We generate a session id client side and store this.

def connect(%{"token" => token, "session_id" => session_id}, socket) do
    case API.Identity.authenticate(token) do
      {:ok, user} ->
        socket =
          socket
          |> assign(:user, user)
          |> assign(:session_id, session_id)
          |> Absinthe.Phoenix.Socket.put_options(
            context: %{
              current_user: user
            }
          )

        {:ok, socket}

      :error ->
        {:error, %{reason: "unauthorized"}}
    end
  end
dlobo commented 3 years ago

yeah, we are storing a session_fingerprint which we get from the authentication library (pow) for each socket connection, so similar to your code above.

However if i had to guess the session_fingerprint is the same for multiple tabs within the same browser (since they are using the same token)

coladarci commented 3 years ago

Yes, typically the session is stored in local storage which is shared across tabs..

dlobo commented 3 years ago

are you doing anything special with publish when you are publishing data onto the subscription? all our subscriptions are based on the organization, so all users from the same organization, get the same data subscriptions

I checked and the fingerprint is different, so we are scoping the "join" (I think)

coladarci commented 3 years ago

Sorry - we are also always publishing to an entire organization for ABSINTHE. We are doing target socket pushes ourselves using presence... So many there's a real bug?

benwilson512 commented 3 years ago

@coladarci you said that each client is getting an identical subscription ID when subscribing? If so, that bit sounds like the bug to me.

coladarci commented 3 years ago

No I was making a bad assumption - I got my signals crossed here confusing how we handle absinthe publishing versus our other publishing w/ sockets directly.

benwilson512 commented 3 years ago

OK so each client is getting a different subscription id, but if you publish, both get it? Can you make this more concrete?

mupkoo commented 3 years ago

I might have the same problem. Here is what I have:

# Web.Schema.Question
object :question_subscriptions do
  field :question_change, :question_change do
    arg :parent_id, non_null(:id)
    config fn %{parent_id: parent_id}, _ -> {:ok, topic: parent_id, context_id: "global"} end
  end
end

# Web.Schema
subscription do
  import_fields :question_subscriptions
end

# After some save operation
Absinthe.Subscription.publish(
  Web.Endpoint,
  %{action: action, question: question},
  question_change: question.parent_id
)

Now depending on how many clients I have connected, I get the same number of pushes and all clients receive the data.

Here are the logs when I have a Safari, Firefox and Chrome windows connected and all three browser windows receive the payload thrice. If I close one of the windows, the number drops down to 2.

---
[debug] QUERY OK source="questions" db=5.2ms idle=35.0ms
SELECT q0."id", q0."name", q0."body", q0."is_visible", q0."parent_id", q0."speaker_id", q0."inserted_at", q0."updated_at" FROM "questions" AS q0 WHERE (q0."id" = $1) [44]
[debug] QUERY OK db=3.7ms queue=3.1ms idle=42.1ms
UPDATE "questions" SET "name" = $1, "updated_at" = $2 WHERE "id" = $3 ["Name", ~U[2021-04-21 09:24:37Z], 44]
[debug] Absinthe Subscription Publication
Field Topic: 1
Subscription id: "__absinthe__:doc:global:2FD0104E90140DE66FC1D279921023676216746C4760C5C3CF35637D6813451B"
Data: %{data: %{"questionChange" => %{"__typename" => "QuestionChange", "action" => "UPDATED", "record" => %{"__typename" => "Question", "body" => "Body", "id" => "44", "insertedAt" => "2021-04-21T08:12:36Z", "isVisible" => true, "name" => "Name"}}}}

[debug] Absinthe Subscription Publication
Field Topic: 1
Subscription id: "__absinthe__:doc:global:2FD0104E90140DE66FC1D279921023676216746C4760C5C3CF35637D6813451B"
Data: %{data: %{"questionChange" => %{"__typename" => "QuestionChange", "action" => "UPDATED", "record" => %{"__typename" => "Question", "body" => "Body", "id" => "44", "insertedAt" => "2021-04-21T08:12:36Z", "isVisible" => true, "name" => "Name"}}}}

[debug] Absinthe Subscription Publication
Field Topic: 1
Subscription id: "__absinthe__:doc:global:2FD0104E90140DE66FC1D279921023676216746C4760C5C3CF35637D6813451B"
Data: %{data: %{"questionChange" => %{"__typename" => "QuestionChange", "action" => "UPDATED", "record" => %{"__typename" => "Question", "body" => "Body", "id" => "44", "insertedAt" => "2021-04-21T08:12:36Z", "isVisible" => true, "name" => "Name"}}}}

[debug] -- Absinthe Phoenix Reply --
{:ok, %{data: %{"updateQuestion" => %{"__typename" => "Question", "body" => "Body", "id" => "44", "insertedAt" => "2021-04-21T08:12:36Z", "isVisible" => true, "name" => "Name", "speaker" => nil}}}}
----------------------------