Hex.pm API Docs Build Status

Express

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

At the moment sends pushes to FCM via HTTP and to APNS via HTTP/2 (with either certificate or JWT).

Uses GenServer in order to balance the load. Default buffer (producer) size is 5000. Default consumer max demand is number_of_available_schedulers * 5 (multiplier can be adjusted).

Installation

# in your mix.exs file

def deps do
  {:express, "~> 1.1.2"}
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")
       ]

Quick examples

APNS

alias Express.APNS

push_message =
  %APNS.PushMessage{
    token: "your_device_token",
    topic: "your_app_topic",
    acme: %{},
    aps: %APNS.PushMessage.Aps{
      badge: 1,
      content_available: 1,
      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

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

Most of options can be changed in config file, but try to start with defaults. Every config option (except basics) has a default value.

Basic

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")
       ]

There is an option:

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

Buffer

There are all possible options for the buffer:

config :express,
       buffer: [
         max_size: 5000,
         consumers_count: 5,
         consumer_demand_multiplier: 5,
         adders_pool_config: [
           {:name, {:local, :buffer_adders_pool}},
           {:worker_module, Express.PushRequests.Adder},
           {:size, 5},
           {:max_overflow, 1}
         ]
       ]

APNS

Possible options for APNS:

you should provide either (cert_path & key_path) or (cert & key) or (key_id & team_id & auth_key_path)

config :express,
       apns: [
         mode: :prod,
         # for requests with jwt
         key_id: System.get_env("EXPRESS_APNS_KEY_ID"),
         team_id: System.get_env("EXPRESS_APNS_TEAM_ID"),
         auth_key_path: System.get_env("EXPRESS_APNS_AUTH_KEY_PATH"),

         # for requests with a certificate
         cert_path: System.get_env("EXPRESS_APNS_CERT_PATH"),
         key_path: System.get_env("EXPRESS_APNS_KEY_PATH"),

         # workers config (if default doesn't meet you requirements)
         workers_pool_config: [
           {:name, {:local, :apns_workers_pool}},
           {:worker_module, Express.APNS.Worker},
           {:size, 8},
           {:max_overflow, 1}
         ]
       ]

FCM

Possible options for FCM:

config :express,
       fcm: [
         api_key: System.get_env("EXPRESS_FCM_API_KEY")

         # workers config (if default doesn't meet you requirements)
         workers_pool_config: [
           {:name, {:local, :fcm_workers_pool}},
           {:worker_module, Express.FCM.Worker},
           {:size, 8},
           {:max_overflow, 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

%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,
  thread_id: String.t,
  alert: Express.APNS.PushMessage.Alert.t | String.t
}

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

FCM

%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)
# 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)

Supervision tree

                                     Application
                                          |
                                    Supervisor 
                                          |
          -----------------------------------------------------------------------
          |                    |                        |                       |
   APNS.Supervisor      FCM.Supervisor       PushRequests.Supervisor      TasksSupervisor
          |                    |                                 |              |
          |         -------------------------                    |   ------------------------
          |         |                       |                    |   |          |           |
          |  FCM.DelayedPushes      :fcm_workers_pool            |  Task       Task        Task
          |                                 |                    |
          |                     ------------------------         |
          |                     |           |          |         |
          |                 FCM.Worker  FCM.Worker  FCM.Worker   |
          |                                                      |
          |                               ----------------------------------------
          |                               |               |                      |
          |                     PushRequests.Buffer   :buffer_adders_pool   PushRequests.ConsumersSupervisor
          |                                               |                      |
          |                                        -------------         ----------------
          |                                        |           |         |              |
          |                                        |           |PushRequests.Consumer  PushRequests.Consumer
          |                                        |           |
          |                               PushRequests.Adder  PushRequests.Adder
          |
      ---------------------------------------------
      |                      |                    |
APNS.JWTHolder      APNS.DelayedPushes    :apns_workers_pool
                                                  |
                                         --------------------------------------
                                         |                |                   |
                                    APNS.Worker      APNS.Worker         APNS.Worker
                                         |                |                   |
                                  APNS.Connection   APNS.Connection    APNS.Connection

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.