defmodule Pigeon do @moduledoc """ HTTP2-compliant wrapper for sending iOS and Android push notifications. ## Getting Started Check the module documentation for your push notification service. - `Pigeon.ADM` - Amazon Android. - `Pigeon.APNS` - Apple iOS. - `Pigeon.FCM` - Firebase Cloud Messaging v1 API. ## Creating Dynamic Runtime Dispatchers Pigeon can spin up dynamic dispatchers for a variety of advanced use-cases, such as supporting dozens of dispatcher configurations or custom connection pools. See `Pigeon.Dispatcher` for instructions. ## Writing a Custom Dispatcher Adapter Want to write a Pigeon adapter for an unsupported push notification service? See `Pigeon.Adapter` for instructions. """ alias Pigeon.Tasks @default_timeout 5_000 @typedoc ~S""" Async callback for push notifications response. ## Examples handler = fn(%Pigeon.ADM.Notification{response: response}) -> case response do :success -> Logger.debug "Push successful!" :unregistered -> Logger.error "Bad device token!" _error -> Logger.error "Some other error happened." end end n = Pigeon.ADM.Notification.new("token", %{"message" => "test"}) Pigeon.ADM.push(n, on_response: handler) """ @type on_response :: (notification -> no_return) | {module, atom} | {module, atom, [any]} @type notification :: %{ :__struct__ => atom(), :__meta__ => Pigeon.Metadata.t(), optional(atom()) => any() } @typedoc ~S""" Options for sending push notifications. - `:on_response` - Optional async callback triggered on receipt of push. See `t:on_response/0` - `:timeout` - Push timeout. Defaults to 5000ms. """ @type push_opts :: [ on_response: on_response | nil, timeout: non_neg_integer ] @doc """ Returns the configured default pool size for Pigeon dispatchers. To customize this value, include the following in your config/config.exs: config :pigeon, :default_pool_size, 5 """ @spec default_pool_size :: pos_integer def default_pool_size() do Application.get_env(:pigeon, :default_pool_size, 5) end @doc """ Returns the configured JSON encoding library for Pigeon. To customize the JSON library, include the following in your config/config.exs: config :pigeon, :json_library, Jason """ @spec json_library :: module def json_library do Application.get_env(:pigeon, :json_library, Jason) end @spec push(pid | atom, notification :: notification, push_opts) :: notification :: struct | no_return @spec push(pid | atom, notifications :: [notification, ...], push_opts) :: notifications :: [struct, ...] | no_return @doc """ Sends a push notification with given options. """ def push(pid, notifications, opts \\ []) def push(pid, notifications, opts) when is_list(notifications) do for n <- notifications, do: push(pid, n, opts) end def push(pid, notification, opts) do if Keyword.has_key?(opts, :on_response) do on_response = Keyword.get(opts, :on_response) notification = put_on_response(notification, on_response) push_async(pid, notification) else timeout = Keyword.get(opts, :timeout, @default_timeout) push_sync(pid, notification, timeout) end end defp push_sync(pid, notification, timeout) do myself = self() ref = :erlang.make_ref() on_response = fn x -> send(myself, {:"$push", ref, x}) end notification = put_on_response(notification, on_response) push_async(pid, notification) receive do {:"$push", ^ref, x} -> x after timeout -> %{notification | response: :timeout} end end defp push_async(pid, notification) do case Pigeon.Registry.next(pid) do nil -> Tasks.process_on_response(%{notification | response: :not_started}) pid -> send(pid, {:"$push", notification}) :ok end end defp put_on_response(notification, on_response) do meta = %{notification.__meta__ | on_response: on_response} %{notification | __meta__: meta} end end