119 lines
3.2 KiB
Elixir
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
|