Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

control livebook

articles/control-livebook.livemd

control livebook

why

attach node 를 마음대로 할 수 있을까?

defmodule Tool do
  use GenServer

  def start() do
    GenServer.start(__MODULE__, [], name: __MODULE__)
  end

  def stop() do
    GenServer.stop(__MODULE__)
  end

  def init(_opts) do
    {:ok, %{}}
  end

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

  def attach_node(node, session_id) do
    GenServer.call(__MODULE__, {:attach_node, node, session_id})
  end

  defp list_lv do
    Process.whereis(:ranch_server)
    |> :sys.get_state()
    |> elem(1)
    |> Enum.find(fn
      {_, {:conns_sup, _}} -> true
      _ -> false
    end)
    |> elem(0)
    |> elem(1)
    |> Process.info(:dictionary)
    |> elem(1)
    |> Enum.filter(fn
      {_, :removed} -> true
      _ -> false
    end)
    |> Enum.map(&(&1 |> elem(0) |> :sys.get_state() |> elem(1)))
    |> Enum.map(fn [_ | {%{channels: c}, _}] -> c end)
    |> Enum.filter(fn
      %{"phoenix:live_reload" => _} -> false
      _ -> true
    end)
    |> Enum.reduce(%{}, &Map.merge/2)
    |> Enum.map(fn {key, val} ->
      channel_id = String.replace(key, "lv:", "")
      Tuple.insert_at(val, 0, channel_id)
      %{id: channel_id, pid: elem(val, 0), ref: elem(val, 1), state: elem(val, 2)}
    end)
  end

  defp list_sockets do
    list_lv()
    |> Enum.map(fn %{pid: pid} -> :sys.get_state(pid) end)
    |> Enum.map(fn state -> state.socket end)
  end

  def handle_call(:list_nodes, _from, state) do
    nodes = Node.list(:hidden) |> Enum.map(&Atom.to_string/1)
    {:reply, nodes, state}
  end

  def handle_call({:attach_node, node, session_id}, _from, state) do
    socket = list_sockets() |> Enum.find(fn socket -> socket.assigns.session.id == session_id end)

    reply =
      case socket do
        nil ->
          nil

        socket ->
          cookie = Node.get_cookie() |> Atom.to_string()

          LivebookWeb.SessionLive.AttachedLive.handle_event(
            "init",
            %{
              "data" => %{"name" => node, "cookie" => cookie}
            },
            socket
          )
      end

    {:reply, reply, state}
  end
end
Node.list(:hidden)