tusker_push/lib/tusker_push/apns.ex

119 lines
3.2 KiB
Elixir

defmodule TuskerPush.Apns do
alias TuskerPush.Registration
require Logger
@spec send(Registration.t(), Map.t()) :: :ok | {:error, term()}
def send(registration, payload) do
with {:ok, body} <- Jason.encode(payload, pretty_print: false),
req <- make_request(registration, body),
{:ok, resp} <- Finch.request(req, TuskerPush.Finch) do
handle_response(resp, registration, payload)
else
{:error, %Finch.Error{reason: :connection_closed}} ->
Logger.warning("Apns Finch connection_closed, retrying in 1s")
Process.sleep(1000)
__MODULE__.send(registration, payload)
{:error, %Finch.Error{reason: :disconnected}} ->
Logger.warning("Apns Finch disconnected, retrying in 1s")
Process.sleep(1000)
__MODULE__.send(registration, payload)
{:error, %Mint.TransportError{reason: :closed}} ->
Logger.warning("Apns Mint transport closed, retrying in 1s")
Process.sleep(1000)
__MODULE__.send(registration, payload)
{:error, reason} ->
{:error, reason}
end
end
@spec make_request(Registration.t(), binary()) :: Finch.Request.t()
defp make_request(registration, body) do
bundle_id = Application.fetch_env!(:tusker_push, :apns)[:bundle_id]
headers = [
{"authorization", "bearer #{TuskerPush.Apns.Token.current()}"},
{"apns-push-type", "alert"},
{"apns-bundle-id", bundle_id},
{"apns-topic", bundle_id},
{"apns-expiration",
DateTime.utc_now() |> DateTime.add(1, :day) |> DateTime.to_unix() |> Integer.to_string()}
]
Finch.build(
:post,
"https://#{host(registration.apns_environment)}/3/device/#{registration.apns_device_token}",
headers,
body
)
end
@spec handle_response(Finch.Response.t(), Registration.t(), Map.t()) :: :ok | {:error, term()}
defp handle_response(resp, registration, payload) do
maybe_log_unique_id(resp, registration.apns_environment)
if resp.status in 200..299 do
:ok
else
info =
case Jason.decode(resp.body) do
{:ok, data} ->
inspect(data)
{:error, _} ->
resp.body
end
case {resp.status, info} do
{403, %{reason: "ExpiredProviderToken"}} ->
Logger.warning("Expired provider token, retrying")
__MODULE__.send(registration, payload)
{410, %{reason: "Unregistered"}} ->
Logger.warning("Device token unregistered")
{:error, :device_token_unregistered}
_ ->
Logger.error("Received #{resp.status} with #{inspect(info)}")
{:error, "unexpected status #{resp.status}"}
end
end
end
@spec maybe_log_unique_id(Finch.Response.t(), :development | :production) :: :ok
defp maybe_log_unique_id(resp, :development) do
resp.headers
|> Enum.find(fn
{"apns-unique-id", _} -> true
_ -> false
end)
|> case do
{_, id} ->
Logger.debug("APNS unique id: #{id}")
_ ->
nil
end
:ok
end
defp maybe_log_unique_id(_resp, :production), do: :ok
@spec host(:development | :production) :: String.t()
def host(:development) do
"api.sandbox.push.apple.com"
end
def host(:production) do
"api.push.apple.com"
end
end