Awesome, so I have a preliminary solution for this. Let me share it.
First, I think the natural place to log the connection is connect/2
:
defmodule MyAppWeb.DriverSocket do
def connect(%{"jwt" => jwt}, socket) do
case verify(jwt) do
{:ok, driver_id} ->
Logger.info("Driver #{driver_id} connected")
{:ok, assign(socket, :driver_id, driver_id)}
_ ->
:error
end
end
end
We’ll log the disconnection in the monitor:
defmodule MyAppWeb.SocketMonitor do
use GenServer
require Logger
def start_link(_) do
GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
end
def monitor(socket_pid, driver_id) do
GenServer.call(__MODULE__, {:monitor, {socket_pid, driver_id}})
end
@impl true
def init(sockets) do
{:ok, sockets}
end
@impl true
def handle_call({:monitor, {socket_pid, driver_id}}, _from, sockets) do
Process.monitor(socket_pid)
{:reply, :ok, Map.put(sockets, socket_pid, driver_id)}
end
@impl true
def handle_info({:DOWN, _ref, :process, socket_pid, _reason}, sockets) do
{driver_id, sockets} = Map.pop(sockets, socket_pid)
Logger.info("Driver #{driver_id} disconnected")
{:noreply, sockets}
end
end
You start the monitor when the application boots:
defmodule MyApp.Application do
use Application
def start(_type, _args) do
children = [
# ...
MyAppWeb.SocketMonitor
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
end
And, finally, the custom definition of init/1
in the socket module (I’ve simplified the one above):
defmodule MyAppWeb.DriverSocket do
def init(state) do
res = {:ok, {_, socket}} = Phoenix.Socket.__init__(state)
MyAppWeb.SocketMonitor.monitor(socket.transport_pid, socket.assigns.driver_id)
res
end
# Must go below, so our init/1 matches first.
use Phoenix.Socket
end
This is redefining an internal method, but the risk seems controlled to me. I guess the test suite of the application would be all broken if Phoenix changes anything related to that.
Of course, it would be really cool that Phoenix had public API for this use case.
It would be awesome to be able to get rid of the warning, “this clause cannot match because a previous clause…”. Let me /cc @OvermindDL1 since he knows all the tricks.