PushX.Response (PushX v0.11.0)

Copy Markdown View Source

A struct representing the response from a push notification request.

Fields

  • :provider - The provider used (:apns or :fcm)
  • :status - The result status (see below)
  • :id - Provider-specific message ID (if available)
  • :reason - Error reason string (if failed)
  • :raw - Raw response body (for debugging)

Status Values

  • :sent - Message was successfully sent
  • :invalid_token - Device token is invalid or expired
  • :expired_token - Device token has expired
  • :unregistered - Device is no longer registered
  • :payload_too_large - Payload exceeds size limit
  • :rate_limited - Too many requests, try again later
  • :server_error - Provider server error
  • :connection_error - Network/connection failure
  • :circuit_open - Circuit breaker is open, provider temporarily blocked
  • :invalid_request - Missing or invalid request parameters (e.g., no :topic for APNS)
  • :auth_error - Authentication failure (e.g., invalid private key, JWT generation failed)
  • :unknown_error - Unrecognized error

Summary

Functions

Maps an APNS error reason to a status atom.

Creates an error response.

Creates an error response with raw data.

Creates an error response with raw data and retry_after value.

Extracts the FCM-specific error code from a decoded error response body.

Maps an FCM error code to a status atom.

Returns true if the error is retryable.

Returns true if the token should be removed from the database.

Creates a successful response.

Returns true if the response indicates success.

Types

status()

@type status() ::
  :sent
  | :invalid_token
  | :expired_token
  | :unregistered
  | :payload_too_large
  | :rate_limited
  | :server_error
  | :connection_error
  | :circuit_open
  | :provider_disabled
  | :invalid_request
  | :auth_error
  | :unknown_error

t()

@type t() :: %PushX.Response{
  id: String.t() | nil,
  provider: :apns | :fcm | :unknown,
  raw: any(),
  reason: String.t() | nil,
  retry_after: non_neg_integer() | nil,
  status: status()
}

Functions

apns_reason_to_status(reason)

@spec apns_reason_to_status(String.t()) :: status()

Maps an APNS error reason to a status atom.

See: https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/handling_notification_responses_from_apns

error(provider, status, reason \\ nil)

@spec error(
  provider :: :apns | :fcm | :unknown,
  status :: status(),
  reason :: String.t() | nil
) :: t()

Creates an error response.

error(provider, status, reason, raw)

@spec error(
  provider :: :apns | :fcm | :unknown,
  status :: status(),
  reason :: String.t() | nil,
  raw :: any()
) :: t()

Creates an error response with raw data.

error(provider, status, reason, raw, retry_after)

@spec error(
  provider :: :apns | :fcm | :unknown,
  status :: status(),
  reason :: String.t() | nil,
  raw :: any(),
  retry_after :: non_neg_integer() | nil
) :: t()

Creates an error response with raw data and retry_after value.

extract_fcm_error_code(arg1)

@spec extract_fcm_error_code(map()) :: String.t() | nil

Extracts the FCM-specific error code from a decoded error response body.

The FCM v1 API wraps the real error code (e.g., "UNREGISTERED") inside the details array with @type "type.googleapis.com/google.firebase.fcm.v1.FcmError", while the top-level "status" contains a generic gRPC code (e.g., "NOT_FOUND").

Returns nil if no FCM-specific error code is found in the details.

Examples

iex> body = %{"error" => %{"status" => "NOT_FOUND", "details" => [%{"@type" => "type.googleapis.com/google.firebase.fcm.v1.FcmError", "errorCode" => "UNREGISTERED"}]}}
iex> PushX.Response.extract_fcm_error_code(body)
"UNREGISTERED"

iex> PushX.Response.extract_fcm_error_code(%{"error" => %{"status" => "INTERNAL"}})
nil

fcm_error_to_status(error_code)

@spec fcm_error_to_status(String.t()) :: status()

Maps an FCM error code to a status atom.

See: https://firebase.google.com/docs/reference/fcm/rest/v1/ErrorCode

retryable?(response)

@spec retryable?(t()) :: boolean()

Returns true if the error is retryable.

Retryable errors:

  • :connection_error - Network/connection failure
  • :rate_limited - Too many requests (with backoff)
  • :server_error - Provider server error (5xx)

Examples

iex> PushX.Response.error(:fcm, :server_error, "Internal") |> PushX.Response.retryable?()
true

iex> PushX.Response.error(:apns, :invalid_token, "bad") |> PushX.Response.retryable?()
false

should_remove_token?(response)

@spec should_remove_token?(t()) :: boolean()

Returns true if the token should be removed from the database.

Examples

iex> PushX.Response.error(:apns, :invalid_token, "BadDeviceToken") |> PushX.Response.should_remove_token?()
true

iex> PushX.Response.error(:fcm, :server_error, "Internal") |> PushX.Response.should_remove_token?()
false

success(provider, id \\ nil)

@spec success(provider :: :apns | :fcm | :unknown, id :: String.t() | nil) :: t()

Creates a successful response.

success?(response)

@spec success?(t()) :: boolean()

Returns true if the response indicates success.

Examples

iex> PushX.Response.success(:apns, "id-123") |> PushX.Response.success?()
true

iex> PushX.Response.error(:fcm, :invalid_token, "bad") |> PushX.Response.success?()
false