Skip to content

esl/sparrow

Repository files navigation

Sparrow

Summary

Sparrow is an Elixir library for push notifications. Build Status Coverage Status

Currently it provides support for the following APIs:

Requirements

  • Elixir 1.13 or higher
  • Erlang OTP 24 or higher

Build sparrow config

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: [...]

Config examples

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

Config options description

FCM specific

  • :path_to_json - is a path to a json file provided by FCM

APNS specific

  • :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

Connection config

  • :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 to nil
  • :reconnect_attempts - number of attempts to reconnect before failing the connection

Connection pool config

  • :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 example

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 example

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
]

APNS pool example

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 example

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
        ]

Include sparrow in your project

defp deps do
    [
      {:sparrow, github: "esl/sparrow", tag: "cc80bbc"},
      ]
  end

Many pools

Sparrow suports many pools in a single :sparrow instance. Tags are used to choose a pool when sending a notification.

Tags

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:

  1. Filter pooltype based on notification type (:fcm or :{apns, :dev} or {:apns, :prod}).
  2. Choose only pools that have all tags (from the function call) included in their tags (from pool configuration).
  3. 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]
  • {:apns, :prod}:
    • pool1: [:prod_pool]

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!

Telemetry

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]

Send your first push notification

  1. Define your config
  2. Start an application
Application.start(:sparrow)
  1. Build and Push the notification 3.1 APNS
        :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()
    3.1 FCM
    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()

How to obtain and use APNS certificate for certificate based authorization?

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`

How to obtain a device token?

Try this.

How to obtain an apns-topic?

Last but not least, to get the 'apns-topic' header value, go to: Xcode -> open your swift app -> General -> Identity -> Bundle Identifier

Good luck :)