# `PushX.APNS`
[🔗](https://github.com/cignosystems/pushx/blob/v0.11.0/lib/push_x/apns.ex#L1)

Apple Push Notification Service (APNS) client.

Sends push notifications to iOS, macOS, watchOS, tvOS devices, and Safari
using HTTP/2 and JWT-based authentication.

## Configuration

Add to your config:

    config :pushx,
      apns_key_id: "ABC123DEFG",
      apns_team_id: "TEAM123456",
      apns_private_key: {:file, "priv/keys/AuthKey.p8"},
      apns_mode: :prod  # or :sandbox

## Usage

    # Simple notification
    PushX.APNS.send(device_token, %{
      "aps" => %{
        "alert" => %{"title" => "Hello", "body" => "World"},
        "sound" => "default"
      }
    }, topic: "com.example.app")

    # Using Message struct
    message = PushX.Message.new("Hello", "World")
    PushX.APNS.send(device_token, message, topic: "com.example.app")

## Safari Web Push

Safari uses APNS for web push notifications. The token format is the same
as iOS (64 hex characters), but the topic uses a `web.` prefix:

    # Safari web push
    PushX.APNS.send(safari_token, payload, topic: "web.com.example.website")

    # Using web notification helper
    payload = PushX.APNS.web_notification("Title", "Body", "https://example.com/page")
    PushX.APNS.send(safari_token, payload, topic: "web.com.example.website")

# `option`

```elixir
@type option() ::
  {:topic, String.t()}
  | {:mode, :prod | :sandbox}
  | {:push_type, String.t()}
  | {:priority, 5 | 10}
  | {:expiration, non_neg_integer()}
  | {:collapse_id, String.t()}
```

# `payload`

```elixir
@type payload() :: map() | PushX.Message.t()
```

# `token`

```elixir
@type token() :: String.t()
```

# `notification`

```elixir
@spec notification(String.t(), String.t(), non_neg_integer() | nil) :: map()
```

Creates a simple notification payload.

## Examples

    iex> PushX.APNS.notification("Hello", "World")
    %{"aps" => %{"alert" => %{"title" => "Hello", "body" => "World"}, "sound" => "default"}}

# `notification_with_data`

```elixir
@spec notification_with_data(String.t(), String.t(), map(), non_neg_integer() | nil) ::
  map()
```

Creates a notification with custom data.

# `send`

```elixir
@spec send(token(), payload(), [option()]) ::
  {:ok, PushX.Response.t()} | {:error, PushX.Response.t()}
```

Sends a push notification to an iOS device with automatic retry.

Uses exponential backoff for transient failures following Apple's best practices.
Permanent failures (bad token, payload too large) are not retried.

## Options

  * `:topic` - Bundle ID (required)
  * `:mode` - `:prod` or `:sandbox` (default: from config)
  * `:push_type` - "alert", "background", "voip", etc. (default: "alert")
  * `:priority` - 5 or 10 (default: 10)
  * `:expiration` - Unix timestamp when notification expires
  * `:collapse_id` - Group notifications with the same ID
  * `:retry` - Enable/disable retry (default: true from config)

## Returns

  * `{:ok, %PushX.Response{}}` on success
  * `{:error, %PushX.Response{}}` on failure

# `send_batch`

```elixir
@spec send_batch([token()], payload(), [option()]) :: [
  {token(), {:ok, PushX.Response.t()} | {:error, PushX.Response.t()}}
]
```

Sends notifications to multiple devices concurrently.

## Options

All standard options plus:
  * `:concurrency` - Max concurrent requests (default: 50)
  * `:timeout` - Timeout per request in ms (default: 30_000)
  * `:validate_tokens` - Validate token format before sending (default: false).
    Invalid tokens get `{:error, %Response{status: :invalid_token}}` without
    hitting the network.

## Returns

A list of `{token, result}` tuples.

# `send_once`

```elixir
@spec send_once(token(), payload(), [option()]) ::
  {:ok, PushX.Response.t()} | {:error, PushX.Response.t()}
```

Sends a push notification without retry.

Use this when you want to handle retries yourself or for testing.

# `silent_notification`

```elixir
@spec silent_notification(map()) :: map()
```

Creates a silent/background notification.

# `web_notification`

```elixir
@spec web_notification(String.t(), String.t(), String.t() | nil, keyword()) :: map()
```

Creates a Safari web push notification payload.

Safari web push uses APNS with a slightly different payload format.
The `url-args` field is used to pass URL arguments to the notification action.

## Arguments

  * `title` - Notification title
  * `body` - Notification body
  * `url` - URL to open when clicked (or URL arguments for Safari)
  * `opts` - Optional keyword list:
    * `:action` - Action button label (default: "View")
    * `:url_args` - List of URL arguments (overrides url parsing)

## Examples

    # Simple web notification
    PushX.APNS.web_notification("New Article", "Check out our latest post", "https://example.com/article/123")

    # With custom action
    PushX.APNS.web_notification("Sale!", "50% off today", "https://shop.com", action: "Shop Now")

    # With explicit URL args
    PushX.APNS.web_notification("Update", "New feature available", nil, url_args: ["features", "v2"])

# `web_notification_with_data`

```elixir
@spec web_notification_with_data(
  String.t(),
  String.t(),
  String.t() | nil,
  map(),
  keyword()
) :: map()
```

Creates a Safari web push notification with custom data.

## Examples

    PushX.APNS.web_notification_with_data(
      "Order Shipped",
      "Your order #123 is on its way",
      "https://example.com/orders/123",
      %{"order_id" => "123"}
    )

---

*Consult [api-reference.md](api-reference.md) for complete listing*
