README.md

[![Hex.pm](https://img.shields.io/hexpm/v/express.svg)](https://hex.pm/packages/express) [![API Docs](https://img.shields.io/badge/api-docs-yellow.svg?style=flat)](http://hexdocs.pm/express/) [![Build Status](https://travis-ci.org/madeinussr/express.svg?branch=master)](https://travis-ci.org/madeinussr/express)

# Express

Library for sending push notifications.
Supports Apple APNS and Google FCM services.

Express - reviewed and remastered [Dufa](https://github.com/madeinussr/dufa) library with some core improvements:

* utilizes poolboy (default pool size = 5, max_overflow = 1)
* supervision tree was reviewed (worker per push (APNS/FCM), connection per supervisor (APNS))
* DI in supervision tree allows to develop, maintain, test and maintain code without pain

_battle tested in production_

## Installation

```elixir
# in your mix.exs file

def deps do
  {:express, "~> 1.0"}
end

# in your config.exs file (more in configuration section below)

config :express,
       apns: [
         mode: :prod,
         cert_path: System.get_env("EXPRESS_APNS_CERT_PATH"),
         key_path: System.get_env("EXPRESS_APNS_KEY_PATH")
       ],
       fcm: [
         api_key: System.get_env("EXPRESS_FCM_API_KEY"),
         collapse_key: System.get_env("EXPRESS_FCM_COLLAPSE_KEY")
       ]
```

## Quick examples

### APNS

```elixir
alias Express.APNS

push_message =
  %APNS.PushMessage{
    token: "your_device_token",
    topic: "your_app_topic",
    acme: %{},
    aps: %APNS.PushMessage.Aps{
      badge: 1,
      content_available: true,
      alert: %APNS.PushMessage.Alert{
        title: "Hello",
        body: "World"
      }
    }
  }

opts = [delay: 5] # in seconds

callback_fun =
  fn(push_message, response) ->
    IO.inspect("==Push message==")
    IO.inspect(push_message)
    IO.inspect("==APNS response==")
    IO.inspect(response)
  end

APNS.push(push_message, opts, callback_fun)
```

### FCM

```elixir
alias Express.FCM

push_message =
  %FCM.PushMessage{
    to: "your_device_registration_id",
    priority: "high",
    content_available: true,
    data: %{},
    notification: %FCM.PushMessage.Notification{
      title: "Hello",
      body: "World"
    }
  }

opts = [delay: 5] # in seconds

callback_fun =
  fn(push_message, response) ->
    IO.inspect("==Push message==")
    IO.inspect(push_message)
    IO.inspect("==FCM response==")
    IO.inspect(response)
  end

FCM.push(push_message, opts, callback_fun)
```

## Configuration

As described earlier, the simpliest configuration for Express might look like:

```elixir
config :express,
       apns: [
         mode: :prod,
         cert_path: System.get_env("EXPRESS_APNS_CERT_PATH"),
         key_path: System.get_env("EXPRESS_APNS_KEY_PATH")
       ],
       fcm: [
         api_key: System.get_env("EXPRESS_FCM_API_KEY"),
         collapse_key: System.get_env("EXPRESS_FCM_COLLAPSE_KEY")
       ]
```

However there are some options:

* for APNS you can provide also `cert` and `key` options (values/content of your certificate and key files respectively)
  the priority of the options is: `cert_path > cert` and `key_path > key`

* you can edit poolboy configuration with `poolboy` option for both APNS and FCM:

```elixir
config :express,
       apns: [
         mode: :prod,
         cert: System.get_env("EXPRESS_APNS_CERT"),
         key: System.get_env("EXPRESS_APNS_KEY")
       ],
       fcm: [
         api_key: System.get_env("EXPRESS_FCM_API_KEY"),
         collapse_key: System.get_env("EXPRESS_FCM_COLLAPSE_KEY")
         poolboy: [
           {:name, {:local, :fcm_supervisors_pool}},
           {:worker_module, Express.FCM.Supervisor},
           {:size, 10}, # default is 5
           {:max_overflow, 3} # default is 1
         ]
       ]
```

## Push message structure

You should construct `%Express.APNS.PushMessage{}` and `%Express.FCM.PushMessage{}`
structures and pass them to `Express.APNS.push/3` and `Express.FCM.push/3` respectively
in order to send a push message.

Express's `Express.APNS.PushMessage` as well as `Express.FCM.PushMessage` conforms official
Apple & Google push message structures, so there should not be any confusion with it.

Here are their structures:

### APNS

```elixir
%Express.APNS.PushMessage{
  token: String.t,
  topic: String.t,
  aps: Express.APNS.PushMessage.Aps.t,
  apple_watch: map(),
  acme: map()
}

%Express.APNS.PushMessage.Aps{
  content_available: pos_integer(),
  mutable_content: pos_integer(),
  badge: pos_integer(),
  sound: String.t,
  category: String.t,
  alert: Express.APNS.PushMessage.Alert.t | String.t
}

%Express.APNS.PushMessage.Alert{
  title: String.t,
  body: String.t
}
```

### FCM

```elixir
%Express.FCM.PushMessage{
  to: String.t,
  registration_ids: [String.t],
  priority: String.t,
  content_available: boolean(),
  collapse_key: String.t,
  data: map(),
  notification: PushMessage.Notification.t
}

%Express.FCM.PushMessage.Notification{
  title: String.t,
  body: String.t,
  icon: String.t,
  sound: String.t,
  click_action: String.t,
  badge: pos_integer(),
  category: String.t
}
```

## Send a push message

In order to send a push message you should to construct a valid message structure,
define a callback function, which will be invoked on provider's response (APNS or FCM)
and pass them along with options to either `Express.FCM.push/3` or `Express.APNS.push/3`
function (see quick examples above).

Nothing to add here, but:

* a callback function has to take two arguments:
  * a push message (which push message structure you tried to send)
  * a push result (response received from a provider and handled by Express)

```elixir
# push result type
@type push_result :: {:ok, %{status: pos_integer(), body: any()}} |
                     {:error, %{status: pos_integer(), body: any()}}
```

* at this moment the single option you can pass with `opts` argument - `delay`
  * it defines a delay in seconds for a push worker (a worker will push a message after that delay)

## Bonus: supervision tree

```elixir
# FCM

                   Express.Supervisor
                           |
          ---------------------------------
          |                               |
APNS Supervisors pool            FCM Supervisors pool
                                          |
                         -----------------------------------
                         |            |                    |
                         |    Workers Supervisor   Workers Supervisor
                         |
                 Workers Supervisor
                         |
                 ------------------
                 |       |        |
              Worker   Worker   Worker


# APNS

               Express.Supervisor
                        |
          -----------------------------
          |                           |
FCM Supervisors pool        APNS Supervisors pool
                                      |
                         ------------------------------------
                         |            |                     |
                         |   Workers Supervisor    Workers Supervisor
                         |
                Workers Supervisor
                         |
                ------------------
                |                |
                |    HTTP2 connection (a supervisor owns a connection)
                |
        ------------------
        |       |        |
    Worker   Worker   Worker (spawned with a connection passed as an argument)

```

## LICENSE

    Copyright © 2017 Andrey Chernykh ( andrei.chernykh@gmail.com )

    This work is free. You can redistribute it and/or modify it under the
    terms of the MIT License. See the LICENSE file for more details.