hpopp

hpopp

Pigeon - iOS and Android push notifications for Elixir

After just over two years in development, this latest version of Pigeon is what I finally consider done in regards to my original vision for the project. v1.1 brings several needed API-breaking changes that should have been rolled into v1.0, but were otherwise overshadowed by the complexity of the FCM http2 implementation. Special thanks to @talklittle for his help on this release.

What is Pigeon? It’s a library for sending iOS, Android, and Amazon Android push notifications. Check out the project: https://github.com/codedge-llc/pigeon

What’s New

Kadabra Bumped to v0.3.2

Kadabra is an HTTP/2 client I wrote from scratch specifically to support Pigeon. v0.3+ brings significant stability improvements (and might actually run a bit faster). This bumps the minimum requirements to Elixir v1.4 and OTP 19.2

Runtime Worker Configs

APNS/FCM/ADM worker connections are now started with custom structs. The old config style is still valid, but it is recommended to use the new style.

# config.exs
config :pigeon, workers: [
  {YourApp.Pigeon, :apns_config},
  ...
]

# your_app_pigeon.ex
defmodule YourApp.Pigeon do
  def apns_config do
    Pigeon.APNS.Config.new(
      name: :apns_default,
      mode: :prod,
      cert: System.get_env("APNS_CERT"),
      key: System.get_env("APNS_KEY"),
      reconnect: false,
      port: 2197,
      ping_period: 300_000
    )
  end
end

Note: :apns_default, :fcm_default, and :adm_default still need to be specified as names in your config if you want pigeon to push to them by default.

Standardized Notification Responses

Notifications now contain the push response on the notification itself. This makes sending batches of notifications more straightforward. APNS and ADM now have a :response key indicating success or failure.

FCM.NotificationResponse has been done away in favor of this new style, though the syntax is a bit different. :response is a keyword list of [{response, "reg id"}] for per-regID responses. There is a second :status key on the notification that indicates whether the whole batch was successful.

Example usage for async response handling on FCM

def handle_response(%FCM.Notification{status: :success, response: responses}) do
  Enum.map(responses, & handle_regid(&1))
end
def handle_response(_else) do
  # some sort of error
end

def handle_regid({:update, {old, new}), do: #update
def handle_regid({:invalid_registration, regid), do: #remove
def handle_regid({:not_registered, regid}), do: #remove
def handle_regid({:unavailable, regid), do: #retry
...

FCM.push(notif, on_response: &handle_response/1)

Roadmap Ahead

Forcus is now going to shift to tackling some of the smaller features that have been on the backburner. Among these:

  • JWT token support for APNS
  • Topic subscriptions for FCM
  • Better onboarding for contributors (mock APNS servers anyone?)

Thanks to everyone who has contributed to the project over the past couple years. As someone who was previously never very involved with open source, I feel like I’ve learned a lot in the process.

Most Liked

hpopp

hpopp

Pigeon 2.0.0-rc.0 Released

It’s been a long time coming, but it’s here! A lot of significant updates and new features, including the FCM v1 API with web-push support and better runtime configuration of push workers.

This will serve as an unofficial migration guide until I’ve written one. Feedback is welcome!

Hexdocs available here.

Using the New Worker Setup

Pigeon 1.x uses default expected atom names for workers and supervised all of them independent from your application. This has been done away with for more of an Ecto-style setup in your supervision tree. The following example is for APNS, but it’s a similar strategy for all of your other push workers.

  1. Create an APNS dispatcher.
# lib/apns.ex
defmodule YourApp.APNS do
  use Pigeon.Dispatcher, otp_app: :your_app
end
  1. (Optional) Add configuration to your config.exs. Note the :adapter key, which tells Pigeon what kind of worker you are setting up.
# config.exs

config :your_app, YourApp.APNS,
  adapter: Pigeon.APNS,
  cert: File.read!("cert.pem"),
  key: File.read!("key_unencrypted.pem"),
  mode: :dev
  1. Start your dispatcher on application boot.
defmodule YourApp.Application do
  @moduledoc false

  use Application

  @doc false
  def start(_type, _args) do
    children = [
      YourApp.APNS
    ]
    opts = [strategy: :one_for_one, name: YourApp.Supervisor]
    Supervisor.start_link(children, opts)
  end
end

If you skipped step two, include your configuration.

defmodule YourApp.Application do
  @moduledoc false

  use Application

  @doc false
  def start(_type, _args) do
    children = [
      {YourApp.APNS, apns_opts()}
    ]
    opts = [strategy: :one_for_one, name: YourApp.Supervisor]
    Supervisor.start_link(children, opts)
  end

  defp apns_opts do
    [
      adapter: Pigeon.APNS,
      cert: File.read!("cert.pem"),
      key: File.read!("key_unencrypted.pem"),
      mode: :dev
    ]
  end
end
  1. Create a notification.
n = Pigeon.APNS.Notification.new("your message", "your device token", "your push topic")
  1. Send the notification. Note: you use the module name! Not Pigeon.APNS.
YourApp.APNS.push(n)

Using this strategy should make it far more flexible for worker setup, including things like loading dynamic push configurations from a database. See Pigeon.Dispatcher for more instructions.

FCM v1.0 API

All legacy Pigeon.FCM functionality has been renamed to Pigeon.LegacyFCM. You will need to do a project-wide rename, or else update to the new configuration and notification structure.

# Before
Pigeon.FCM.Notification.new("reg ID", %{"body" => "test message"})

# After
Pigeon.LegacyFCM.Notification.new("reg ID", %{"body" => "test message"})

The new Pigeon.FCM.Notification exposes all of the v1 notification attributes you might expect to use, but makes no guarantees about structure and typing, apart from the top-level keys. This gets you maximum flexibility to use the API how you need, including things like custom APNS options or web push.

Notification targets replace the typical RegID string. You now pass a tuple for what you want, such as {:token, "regid"}, {:topic, "yourtopic"}, or {:condition, "yourcondition"}.

Pigeon.FCM.Notification.new({:token, "reg ID"})
%Pigeon.FCM.Notification{
  data: nil,
  notification: nil,
  target: {:token, "reg ID"}
}

Pigeon.FCM.Notification.new({:topic, "example"})
%Pigeon.FCM.Notification{
  data: nil,
  notification: nil,
  target: {:topic, "example"}
}

Notification moduledocs here. You also might want to reference the Firebase v1 Message API as well.

Custom Adapters

Probably the coolest feature of this update is the new Pigeon.Adapter behaviour, which all Pigeon workers implement. What does this mean long term? Anybody can write a push adapter for Pigeon, whether that’s a third party push service, SMS, webhooks, or anything else. Expect better documentation on this soon, but in the meantime if you’re interested in writing an adapter, reach out to me and I can help!

Here is an example of the Pigeon.Sandbox adapter, which is useful for tests and local development. It will mark any push as :success and return it.

defmodule Pigeon.Sandbox do
  import Pigeon.Tasks, only: [process_on_response: 1]

  @behaviour Pigeon.Adapter

  @impl true
  def init(opts \ []) do
    {:ok, opts}
  end

  @impl true
  def handle_info(_msg, state) do
    {:noreply, state}
  end

  @impl true
  def handle_push(%{response: nil} = notification, on_response, _state) do
    process_on_response(on_response, %{notification | response: :success})
    {:noreply, state}
  end

  def handle_push(notification, on_response, state) do
    process_on_response(on_response, notification)
    {:noreply, state}
  end
end

Worker Pooling By Default

Now that APNS allows multiple persistent connections, worker pooling has been enabled by default. The default value is 5 for all services, but this can be overridden on a per-dispatcher basis or globally.

# Just the FCM worker
config :your_app, YourApp.FCM,
  pool_size: 10

# Globally
config :pigeon, :default_pool_size, 10

Next Steps

This release removes a lot of the cruft from design choices that were made in the early days of Pigeon. It is highly encouraged to adopt the new FCM v1 API. Many users of Pigeon jumped through hoops to get dynamic runtime workers started. Feedback is welcome on the new style-- hopefully it’s far more pleasant!

So what’s next feature-wise? Telemetry metrics. Expect it in a 2.1 release!

hpopp

hpopp

v1.2.0 Released

Now with support for APNS JWT configuration! Special thanks to @Lankester for the feature.

It’s very simple to configure! Pigeon can auto-detect whether you are using a certificate or token config.

config :pigeon, :apns,
  apns_default: %{
    key: "AuthKey.p8",
    key_identifier: "ABC1234567",
    team_id: "DEF8901234",
    mode: :dev
  }

:key is configured just like :cert, which can be a file path, file contents, string or tuple.

Kadabra Updates

Pigeon now uses Kadabra 0.4.2, which brings more http2 stability improvements. Various race condition errors during connection shutdown should now be fixed!

Roadmap Ahead

The only remaining major feature is FCM web push encryption support. Once implemented, focus will shift to improving the reliability of error handling for failed push notifications. There are certain Kadabra API changes in the works that will make this task much easier. It’s finally starting to mature into a more general purpose http2 client. Take a look at the repo if you haven’t already.

10
Post #5
kokolegorille

kokolegorille

Does pigeon implement the full IPoAC Protocol? :slight_smile:

Where Next?

Popular in Announcing Top

OvermindDL1
I created a new library (rather I pulled out a couple files from my big project), it manages an operating system PID file for the BEAM. ...
New
dbern
I’m excited to announce that TaxJar has developed and open-sourced DateTimeParser. We developed it because we found a need to parse user ...
New
mplatts
With HEEX released we decided to start a components library using Tailwind CSS - check it out here: Petal Components. We also have a boi...
New
devonestes
Introducing assertions, the library that helps you write really great test assertions! GitHub: GitHub - devonestes/assertions: Helpful a...
New
brainlid
LangChain is short for Language Chain. An LLM, or Large Language Model, is the “Language” part. This library makes it easier for Elixir a...
New
Crowdhailer
Experimenting with this code. OK.try do user <- fetch_user(1) cart <- fetch_cart(1) order = checkout(cart, user) save_orde...
New
MRdotB
I needed to reuse React components from my Chrome extension in my Phoenix/LiveView backend. I noticed that for Svelte/Vue, there are live...
New
treble37
Just looking for a little feedback on a tiny helper library I built - Sometimes I find the need to convert maps with atom keys to maps w...
New
woylie
I released Doggo, a collection of unstyled Phoenix components. https://github.com/woylie/doggo Features Unstyled Phoenix components....
New
mattludwigs
Grizzly is a library for working with Z-Wave devices. Z-Wave is a low-frequency radio protocol for controlling smart home devices on a me...
New

Other popular topics Top

danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 29377 241
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
Nvim
Anybody knows a comprehensive comparison of Django and Phoenix, thanks for the help. Where are they similar? Where do they differ the m...
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New
Qqwy
Update: How to use the Blogs & Podcasts section You can post links to your blog posts or podcasts either in one of the Official Blog...
3271 126479 1222
New
AstonJ
Seen any cool LiveView demos, sample apps or examples? Please post them here! :003:
New

We're in Beta

About us Mission Statement