defmodule HTTP.EventSource do @moduledoc """ Browser-like EventSource client API for Elixir. Events are delivered as messages to the owner process: {HTTP.EventSource, source, %HTTP.EventSource.Event.Open{}} {HTTP.EventSource, source, %HTTP.EventSource.Event.Message{}} {HTTP.EventSource, source, %HTTP.EventSource.Event.Error{}} Custom server-sent event names are delivered through the message event's `type` field. """ alias HTTP.EventSource.Connection alias HTTP.EventSource.Options defstruct pid: nil, ref: nil, url: nil, with_credentials: false @connecting 0 @open 1 @closed 2 @call_timeout 5_000 @type t :: %__MODULE__{ pid: pid() | nil, ref: reference() | nil, url: String.t() | nil, with_credentials: boolean() } @spec connecting() :: 0 def connecting, do: @connecting @spec open() :: 1 def open, do: @open @spec closed() :: 2 def closed, do: @closed @spec new(String.t() | URI.t(), keyword() | map()) :: t() | {:error, term()} def new(url, init \\ []) do ref = make_ref() with {:ok, options} <- Options.new(url, put_ref(init, ref)), {:ok, pid} <- DynamicSupervisor.start_child( HTTP.EventSource.ConnectionSupervisor, {Connection, options} ) do %__MODULE__{ pid: pid, ref: ref, url: options.url, with_credentials: options.with_credentials } end end @spec url(t()) :: String.t() | nil def url(%__MODULE__{url: url}), do: url @spec with_credentials(t()) :: boolean() def with_credentials(%__MODULE__{with_credentials: with_credentials}), do: with_credentials @spec ready_state(t()) :: 0 | 1 | 2 def ready_state(source), do: connection_call(source, :ready_state, @closed) @spec last_event_id(t()) :: String.t() def last_event_id(source), do: connection_call(source, :last_event_id, "") @spec reconnect_time(t()) :: non_neg_integer() def reconnect_time(source), do: connection_call(source, :reconnect_time, 0) @spec close(t()) :: :ok def close(source), do: connection_call(source, :close, :ok) defp connection_call(%__MODULE__{pid: pid}, request, default) when is_pid(pid) do GenServer.call(pid, request, @call_timeout) catch :exit, _reason -> default end defp connection_call(_source, _request, default), do: default defp put_ref(init, ref) when is_map(init), do: Map.put(init, :ref, ref) defp put_ref(init, ref) when is_list(init), do: Keyword.put(init, :ref, ref) end