lib/bbox/server.ex

defmodule Bbox.Server do
  use GenServer
  require Logger

  alias Bbox.Peers

  def start_link(state) do
    GenServer.start_link(__MODULE__, Keyword.put(state, :peers, []), name: __MODULE__)
  end

  @impl true
  def init(state) do
    port = Keyword.fetch!(state, :port)

    Process.flag(:trap_exit, true)

    {:ok, socket} = :gen_tcp.listen(port, [:binary, packet: :line, active: true, reuseaddr: true])

    address = "127.0.0.1:#{port}"

    Peers.insert(%{address: address})

    Logger.info("Listening on #{address}")

    send(self(), {:accept, socket})

    {:ok, state}
  end

  @impl true
  def handle_info({:accept, socket}, state) do
    {:ok, peer} = :gen_tcp.accept(socket)
    :gen_tcp.send(peer, "Welcome to Bbox\n")

    peers =
      state
      |> Keyword.fetch!(:peers)
      |> Enum.concat([peer])

    send(self(), {:accept, socket})

    {:noreply, Keyword.put(state, :peers, peers)}
  end

  @impl true
  def handle_info({:tcp, _socket, packet}, state) do
    state
    |> Keyword.fetch!(:peers)
    |> Enum.each(fn peer ->
      :gen_tcp.send(peer, packet)
    end)

    {:noreply, state}
  end

  @impl true
  def handle_info({:tcp_closed, _}, state), do: {:stop, :normal, state}

  @impl true
  def handle_info({:tcp_error, _}, state), do: {:stop, :normal, state}

  @impl true
  def handle_call(:peers, _from, state), do: {:reply, Keyword.fetch!(state, :peers), state}

  def peers() do
    GenServer.call(__MODULE__, :peers)
  end
end