Is there a better way to maintain online status of each user?

Hello.

I made an online status indicator function with elixir.
It manages online status of users with database and channels.


Codes and a table and json examples

online_channel.ex

defmodule PappapWeb.OnlineChannel do
  use Phoenix.Channel

  require Logger

  alias Pappap.Accounts

  def join("online", _payload, socket) do
    {:ok, socket}
  end

  def handle_in("online", payload, socket) do
    %{"sender" => sender} = payload

    case Accounts.get_user_by_user_id(sender) do
      [] ->
        Logger.info("Unknown user")
      user ->
        user
        |> hd()
        |> Accounts.update_user(%{is_online: true})
    end

    broadcast!(socket, "online", %{user: sender})
    {:noreply, socket}
  end

  def handle_in("offline", payload, socket) do
    %{"sender" => sender} = payload

    case Accounts.get_user_by_user_id(sender) do
      [] ->
        Logger.info("Unknown user")
      user ->
        user
        |> hd()
        |> Accounts.update_user(%{is_online: false})
    end

    broadcast!(socket, "offline", %{user: sender})
    {:noreply, socket}
  end
end

database

# select * from users;
 id | user_id | is_online |     inserted_at     |     updated_at
----+---------+-----------+---------------------+---------------------
  1 | 1       | t         | 2020-08-05 04:25:55 | 2020-08-05 11:03:36
  2 | 2       | f         | 2020-08-05 04:25:55 | 2020-08-06 14:51:06
(2 rows)

json

{"topic":"online", "ref":1, "payload": {"sender": "2"}, "event":"phx_join"}
or
{"topic":"online", "ref":1, "payload": {"sender": "2"}, "event":"online"}
or
{"topic":"online", "ref":1, "payload": {"sender": "2"}, "event":"offline"}

The server changes is_online by a json sent from a user through websocket. When user 2 joins a topic online, he need to send 2 json data.

{"topic":"online", "ref":1, "payload": {"sender": "2"}, "event":"phx_join"}
{"topic":"online", "ref":1, "payload": {"sender": "2"}, "event":"online"}

The former one just means ā€œuser 2 joined topic onlineā€, and the latter one means like ā€œuser 2 is onlineā€.
And then, is_online turns true by the latter json. The former one does not have meanings more than ā€œuser 2 joined topic onlineā€.

It was about how to turn online. Then, the next is about how to turn offline.

To be offline in the system, a user has to send json before leaving a channel and disconnecting from the server. This is the json.

{"topic":"online", "ref":1, "payload": {"sender": "2"}, "event":"offline"}

It means like ā€œuser 2 is offlineā€.
handle_in("offline", payload, socket) works when the json is sent, and is_online of user 2 turns false.
With those processes, the system can manage online status of users.

But I think the system has a problem.

It cannot manage online status without handle_in/3. If a client get disconnected from the server without sending json. The online status does not turn offline. So I want to have the system send json at the disconnecting event. But I donā€™t think phoenix can get message at disconnecting.

If thereā€™s a better way of managing online status, teach me how!
(Let me know my English feels weird, because Iā€™m from Japan)

Have you looked into considered Phoenix Presence?

Its build into Phoenix.
https://hexdocs.pm/phoenix/Phoenix.Presence.html

I have a tutorial about it here:

8 Likes

Can always have an ETS table with the user_id as a key and a timestamp of their last online heartbeat as an index. Then a scheduled process can sweep the ets table and remove users who have ā€˜disconnectedā€™ (current timestamp is older than a set amount of time from the current time). Online status can just be if the user is in the ets table.

Its ripe for race conditions if ya use only dirty operations :slight_smile:

2 Likes