lib/absinthe_websocket/subscription_server.ex

defmodule AbsintheWebSocket.SubscriptionServer do
  use GenServer
  require Logger

  def start_link(args, opts) do
    socket = Keyword.get(args, :socket)
    subscriber = Keyword.get(args, :subscriber)
    state = %{
      connected?: false,
      disconnected_casts: [],
      socket: socket,
      subscriber: subscriber,
      subscriptions: %{},
    }
    GenServer.start_link(__MODULE__, state, opts)
  end

  def init(state) do
    {:ok, state}
  end

  def subscribe(mod, subscription_name, callback, query, variables \\ []) do
    GenServer.cast(mod, {:subscribe, subscription_name, callback, query, variables})
  end

  def unsubscribe(mod, subscription_name) do
    GenServer.cast(mod, {:unsubscribe, subscription_name})
  end

  def handle_cast({:subscribe, _, _, _, _} = message, %{connected?: false} = state) do
    state = Map.put(state, :disconnected_casts, state.disconnected_casts ++ [message])

    {:noreply, state}
  end

  def handle_cast({:subscribe, subscription_name, callback, query, variables}, %{socket: socket, subscriptions: subscriptions} = state) do
    AbsintheWebSocket.WebSocket.subscribe(socket, self(), subscription_name, query, variables)

    subscriptions = Map.put(subscriptions, subscription_name, [callback])
    state = Map.put(state, :subscriptions, subscriptions)

    {:noreply, state}
  end

  def handle_cast({:unsubscribe, _} = message, %{connected?: false} = state) do
    state = Map.put(state, :disconnected_casts, state.disconnected_casts ++ [message])

    {:noreply, state}
  end

  def handle_cast({:unsubscribe, subscription_name}, %{socket: socket, subscriptions: subscriptions} = state) do
    AbsintheWebSocket.WebSocket.unsubscribe(socket, self(), subscription_name)

    subscriptions = Map.delete(subscriptions, subscription_name)
    state = Map.put(state, :subscriptions, subscriptions)

    {:noreply, state}
  end

  # Incoming Notifications (from AbsintheWebSocket.WebSocket)
  def handle_cast({:subscription, subscription_name, response}, %{subscriptions: subscriptions} = state) do
    # handle_subscription(subscription_name, response)

    Map.get(subscriptions, subscription_name, [])
    |> Enum.each(fn(callback) -> callback.(response) end)

    {:noreply, state}
  end

  def handle_cast({:joined}, %{subscriber: subscriber} = state) do
    apply(subscriber, :subscribe, [])

    Enum.each(state.disconnected_casts, &GenServer.cast(self(), &1))
    state = Map.merge(state, %{connected?: true, disconnected_casts: []})

    {:noreply, state}
  end

  def handle_cast({:disconnected}, state) do
    {:noreply, Map.put(state, :connected?, false)}
  end
end