Sparrow is an Elixir library for push notifications.
Currently it provides support for the following APIs:
- Elixir 1.13 or higher
- Erlang OTP 24 or higher
This section describes how to write a config file for Sparrow. If you wish to use just one of the following services, do not include the other in the config.
config :sparrow,
fcm: [...],
apns: [...]
FCM only:
config :sparrow, fcm: [
[
path_to_json: "path/to/google-services.json"
]
]
APNS only:
config :sparrow,
apns: [
dev:
[
[
auth_type: :certificate_based,
cert: "path/to/apns/cert.pem",
key: "path/to/apns/key.pem"
]
],
prod:
[
[
auth_type: :token_based,
token_id: :some_atom_id,
tags: [:production, :token_based_auth]
],
[
auth_type: :token_based,
token_id: :other_atom_id,
tags: [:other_production_pool, :token_based_auth]
]
],
tokens:
[
[
token_id: :some_atom_id,
key_id: "FAKE_KEY_ID",
team_id: "FAKE_TEAM_ID",
p8_file_path: "path/to/file/token.p8"
],
[
token_id: :other_atom_id,
key_id: "OTHER_FAKE_KEY_ID",
team_id: "OTHER_FAKE_TEAM_ID",
p8_file_path: "path/to/file/other/token.p8"
]
]
]
Both FCM and APNS:
config :sparrow,
fcm: [
[
path_to_json: "path/to/fcm_token.json"
]
],
apns: [
dev:
[
[
auth_type: :certificate_based,
cert: "path/to/apns/cert.pem",
key: "path/to/apns/key.pem"
]
]
]
:path_to_json
- is a path to a json file provided by FCM
-
:auth_type
- defines the authentication type, the allowed values are::token_based
,:certificate_based
-
:token_based
requires setting::token_id
- is a unique atom referring to a token with the same:token_id
in config
-
:certificate_based
requires setting::cert
- path to the certificate file provided by APNS:key
- path to the key file provided by APNS
-
:endpoint
- service uri:port
- service port:tls_opts
- passed to erlang ssl module (see DATA TYPES -> ssl_option()):ping_interval
- number of miliseconds between each ping, to switch ping off set:ping_interval
tonil
:reconnect_attempts
- number of attempts to reconnect before failing the connection
:pool_name
- defines pool name, not recommended - please use tags instead:tags
- see the tags section:worker_num
- number of workers in a pool:raw_opts
- opts passed directly to wpool
fcm_config =
[
[
# Authentication
path_to_json: "path/to/fcm_token.json", # mandatory, path to FCM authentication JSON file
# Connection config
endpoint: "fcm.googleapis.com", # optional
port: 443, # optional
tls_opts: [], # optional
ping_interval: 5000, # optional
reconnect_attempts: 3, # optional
# Pool config
tags: [], # optional
worker_num: 3, # optional
raw_opts: [] # optional, options passed directly to wpool
]
]
apns_config =
[
dev: [apns_pool_1, apns_pool_2 ], # list of apns_pool_configs by default set to APNS development endpoint, is a list of APNS pools
prod: [apns_pool_3 ], # list of apns_pool_configs by default set to APNS production endpoint, is a list of APNS pools
tokens: [apns_token_1, apns_token_2 ] # optional, is a list of APNS tokens
]
Token based authentication example:
apns_pool = [
# Token based authentication
auth_type: :token_based, # mandatory, :token_based or :certificate_based
token_id: :some_atom_id, # mandatory, token with the same id must be in `tokens` in `apns_config`
# Connection config
endpoint: "api.development.push.apple.com", # optional
port: 443, # optional
tls_opts: [], # optional
ping_interval: 5000, # optional
reconnect_attempts: 3, # optional
# pool config
tags: [:first_batch_clients, :beta_users], # optional
worker_num: 3, # optional
raw_opts: [], # optional
]
Certificate based authentication example:
apns_pool = [
# Certificate based authentication
auth_type: :certificate_based, # mandatory, :token_based or :certificate_based
cert: "path/to/apns/cert.pem", # mandatory, path to certificate file provided by APNS
key: "path/to/apns/key.pem", # mandatory, path to key file provided by APNS
# Connection config
endpoint: "api.push.apple.com", # optional
port: 443, # optional
tls_opts: [], # optional
ping_interval: 5000, # optional
reconnect_attempts: 3, # optional
# pool config
tags: [:another_batch_clients], # optional
worker_num: 3, # optional
raw_opts: [] # optional
]
apns_token = [
token_id: :some_atom_id, # mandatory, the same as in APNSPOOL
key_id: "FAKE_KEY_ID", # mandatory, data obtained form APNS account
team_id: "FAKE_TEAM_ID", # mandatory, data obtained form APNS account
p8_file_path: "path/to/file/token.p8" # mandatory, path to file storing APNS token
]
defp deps do
[
{:sparrow, github: "esl/sparrow", tag: "cc80bbc"},
]
end
Sparrow suports many pools in a single :sparrow
instance.
Tags are used to choose a pool when sending a notification.
Tags is a mechanism allowing to choose a pool to send a notification from.
Each pool has a defined list of tags ([]
as default).
The algorithm has the following steps:
- Filter pooltype based on notification type (
:fcm
or:{apns, :dev}
or{:apns, :prod}
). - Choose only pools that have all tags (from the function call) included in their tags (from pool configuration).
- Choose first of the filtered pools.
Example: Let's say you have the following pools:
{:apns, :dev}
:- pool1:
[:test_pool, :dev_pool, :homer]
, - pool2:
[:test_pool, :dev_pool, :bart]
, - pool3:
[:test_pool, :dev_pool, :ned, :bart]
- pool1:
{:apns, :prod}
:- pool1:
[:prod_pool]
- pool1:
Lets assume the notification type is {:apns, :dev}
.
If you pass []
, pool1 is chosen.
If you pass [:homer]
, pool1 is chosen.
If you pass [:bart]
, pool2 is chosen.
If you pass [:ned]
, pool3 is chosen.
If you pass [:test_pool]
, pool1 is chosen.
If you pass [:test_pool, :homer]
, pool1 is chosen.
If you pass [:test_pool, :dev_pool, :homer]
, pool1 is chosen.
If you pass [:test_pool, :dev_pool, :ned]
, pool3 is chosen.
If you pass [:not_existing, :set_of_tags]
, {:error, :configuration_error}
is returned.
It is not recommended to choose a pool based on pools order!
Sparrow supports telemetry. Emitted events are defined with following tags:
[:sparrow, :h2_worker, :init]
[:sparrow, :h2_worker, :terminate]
[:sparrow, :h2_worker, :conn_lost]
[:sparrow, :h2_worker, :request_error]
[:sparrow, :h2_worker, :request_success]
[:sparrow, :h2_worker, :conn_success]
[:sparrow, :h2_worker, :conn_fail]
[:sparrow, :pools_warden, :init]
[:sparrow, :pools_warden, :terminate]
[:sparrow, :pools_warden, :choose_pool]
[:sparrow, :pools_warden, :pool_down]
[:sparrow, :pools_warden, :add_pool]
There are also events measuring the duration of a few chosen function calls:
[:sparrow, :push, :api]
[:sparrow, :push, :apns]
[:sparrow, :push, :fcm]
[:sparrow, :h2_worker, :handle]
- Define your config
- Start an application
Application.start(:sparrow)
- Build and Push the notification
3.1 APNS
3.1 FCM
:ok = "my_device_token" |> Sparrow.APNS.Notification.new(:dev) |> Sparrow.APNS.Notification.add_title("my first notification title") |> Sparrow.APNS.Notification.add_body("my first notification body") # |> Sparrow.APNS.Notification.add_... |> Sparrow.API.push()
android = Sparrow.FCM.V1.Android.new() |> Sparrow.FCM.V1.Android.add_title("my first notification title") |> Sparrow.FCM.V1.Android.add_body("my first notification body") webpush = Sparrow.FCM.V1.Webpush.new("www.my.test.link.com") |> Sparrow.FCM.V1.Webpush.add_title("my first notification title") |> Sparrow.FCM.V1.Webpush.add_body("my first notification body") notification = Sparrow.FCM.V1.Notification.new(:topic, "news") |> Sparrow.FCM.V1.Notification.add_android(android) |> Sparrow.FCM.V1.Notification.add_webpush(webpush) Sparrow.API.push()
Pre Requirements:
- MacOS with Xcode installed
- Apple mobile device
- Tutorial (*) requirements
First, follow this tutorial (*), specifically the "Step 3, get APNS certificate" section.
When you reach point where you have exampleName.cer
file, import it to Keychain Access:
File -> Import Items... -> Chose exampleName.cer
Next, export the certificate you just imported as exampleName.p12
.
Note: you can just go with no password by pressing Enter. If you enter a password, remember it.
I shall refer to this password as (pass1) later in tutorial.
Open terminal, go to your exampleName.p12
file location
$ cd my/p12/file/location
Next convert .p12
to .pem
:
$ openssl pkcs12 -in `exampleName.p12` -out `exampleName.pem`
Type (pass1), and then exampleName.pem
file password (pass2), which cannot be empty.
Next extract key:
openssl rsa -in `exampleName.pem` -out `exampleKey.pem`
Try this.
Last but not least, to get the 'apns-topic' header value, go to: Xcode -> open your swift app -> General -> Identity -> Bundle Identifier
Good luck :)