From 6e93aba20e7c0ab3f2b9274335812286d0b31b12 Mon Sep 17 00:00:00 2001 From: Henry Popp Date: Mon, 7 Sep 2026 21:24:16 -0500 Subject: [PATCH 1/3] chore: 2026 Q3 wardening --- .github/workflows/ci.yml | 36 +++-- .tool-versions | 4 +- CHANGELOG.md | 9 ++ LICENSE | 2 +- README.md | 17 ++- config/dev.exs | 4 +- config/dialyzer.ignore-warnings | 2 - docs/ADM Amazon Android.md | 78 ----------- docs/APNS Apple iOS.md | 238 -------------------------------- docs/FCM Android.md | 125 ----------------- docs/Migrating to v1-1-0.md | 34 ----- docs/Migrating-to-v2-0-0.md | 4 +- lib/pigeon/apns.ex | 4 +- lib/pigeon/apns/error.ex | 2 - lib/pigeon/apns/token.ex | 2 - lib/pigeon/configurable.ex | 4 - lib/pigeon/fcm.ex | 5 +- lib/pigeon/fcm/config.ex | 36 +---- lib/pigeon/http.ex | 7 +- mix.exs | 21 +-- mix.lock | 46 ++---- test/pigeon/apns/error_test.exs | 4 +- test/pigeon/fcm_test.exs | 1 - 23 files changed, 86 insertions(+), 599 deletions(-) delete mode 100644 config/dialyzer.ignore-warnings delete mode 100644 docs/ADM Amazon Android.md delete mode 100644 docs/APNS Apple iOS.md delete mode 100644 docs/FCM Android.md delete mode 100644 docs/Migrating to v1-1-0.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b7d05ef6..9cb46462 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,13 +12,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + - name: Use Node.js 22.x + uses: actions/setup-node@v4 with: - ref: ${{ github.head_ref }} - - name: Use Node.js 18.x - uses: actions/setup-node@v3 - with: - node-version: 18.x + node-version: 22.x - name: Install Prettier run: npm install --global prettier - name: Run Prettier @@ -27,14 +25,14 @@ jobs: name: Format/Credo runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Elixir uses: erlef/setup-beam@v1 with: - elixir-version: "1.18.1" - otp-version: "27.2" + elixir-version: "1.20" + otp-version: "29" - name: Restore dependencies cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: deps key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} @@ -46,17 +44,25 @@ jobs: - name: Run Credo run: mix credo test: - name: Test + name: Test (Elixir ${{ matrix.elixir }} / OTP ${{ matrix.otp }}) runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - elixir: "1.16" + otp: "26" + - elixir: "1.20" + otp: "29" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Elixir uses: erlef/setup-beam@v1 with: - elixir-version: "1.18.1" - otp-version: "27.2" + elixir-version: ${{ matrix.elixir }} + otp-version: ${{ matrix.otp }} - name: Restore dependencies cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: deps key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} diff --git a/.tool-versions b/.tool-versions index c1339684..1e6eab36 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -elixir 1.18.3-otp-27 -erlang 27.3.1 +elixir 1.20.4-otp-29 +erlang 29.0.6 diff --git a/CHANGELOG.md b/CHANGELOG.md index 2af47a17..a96404fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Use Mint for all HTTP/1 and HTTP/2 connections. This replaces the use of `:httpoison` and `:kadabra`. ([#296](https://github.com/codedge-llc/pigeon/pull/296)) +- Minimum supported Elixir version is now `1.16`. Pigeon supports every Elixir version + that still receives security patches, and the minimum moves forward as Elixir drops + older branches. +- Support Elixir 1.20 and Erlang/OTP 29. + +**Removed** + +- Unpublished v1 guides in `docs/`. The `Pigeon.ADM`, `Pigeon.APNS`, and `Pigeon.FCM` + module docs cover v2 setup. The v1 guides remain on the `v1.6` branch. **Fixed** diff --git a/LICENSE b/LICENSE index 44daab0b..fa3e9e7b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2015-2025 Codedge LLC (https://www.codedge.io/) +Copyright (c) 2015-2026 Codedge LLC (https://www.codedge.io/) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 694b531c..2836e02b 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,17 @@ -![logo](https://raw.githubusercontent.com/codedge-llc/pigeon/master/docs/logo.png) +![logo](https://raw.githubusercontent.com/codedge-llc/pigeon/main/docs/logo.png) > iOS (APNS), Android (FCM), and Amazon Android (ADM) push notifications for Elixir. [![CI](https://github.com/codedge-llc/pigeon/actions/workflows/ci.yml/badge.svg)](https://github.com/codedge-llc/pigeon/actions/workflows/ci.yml) [![Version](https://img.shields.io/hexpm/v/pigeon.svg)](https://hex.pm/packages/pigeon) [![Total Downloads](https://img.shields.io/hexpm/dt/pigeon.svg)](https://hex.pm/packages/pigeon) -[![License](https://img.shields.io/hexpm/l/pigeon.svg)](https://github.com/codedge-llc/pigeon/blob/master/LICENSE) -[![Last Updated](https://img.shields.io/github/last-commit/codedge-llc/pigeon.svg)](https://github.com/codedge-llc/pigeon/commits/master) +[![License](https://img.shields.io/hexpm/l/pigeon.svg)](https://github.com/codedge-llc/pigeon/blob/main/LICENSE) +[![Last Updated](https://img.shields.io/github/last-commit/codedge-llc/pigeon.svg)](https://github.com/codedge-llc/pigeon/commits/main) [![Documentation](https://img.shields.io/badge/documentation-gray)](https://hexdocs.pm/pigeon/) ## Installation -Add `:pigeon` and as a `mix.exs` dependency: +Add `:pigeon` as a `mix.exs` dependency: ```elixir def deps do @@ -21,6 +21,9 @@ def deps do end ``` +Pigeon supports every Elixir version that still receives security patches, currently 1.16 and later. +The minimum version moves forward when Elixir drops a branch from support. + ## Upgrading from v1.6 See the [migration guide](./docs/Migrating-to-v2-0-0.md) for instructions. @@ -51,7 +54,7 @@ See [Pigeon.Adapter](https://hexdocs.pm/pigeon/Pigeon.Adapter.html) for instruct ### Testing Unit tests can be run with `mix test` or `mix coveralls.html`. Environment variables will need to be set for -various credentials. See [config/test.exs](https://github.com/codedge-llc/pigeon/blob/master/config/test.exs) +various credentials. See [config/test.exs](https://github.com/codedge-llc/pigeon/blob/main/config/test.exs) for the full list. ### Formatting @@ -66,6 +69,6 @@ Git commit subjects use the [Karma style](http://karma-runner.github.io/5.0/dev/ ## License -Copyright (c) 2015-2025 Codedge LLC (https://www.codedge.io/) +Copyright (c) 2015-2026 Codedge LLC (https://www.codedge.io/) -This library is MIT licensed. See the [LICENSE](https://github.com/codedge-llc/pigeon/blob/master/LICENSE) for details. +This library is MIT licensed. See the [LICENSE](https://github.com/codedge-llc/pigeon/blob/main/LICENSE) for details. diff --git a/config/dev.exs b/config/dev.exs index 352d4af0..76e4bbaa 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -14,5 +14,5 @@ config :pigeon, PigeonTest.APNS, config :pigeon, PigeonTest.FCM, adapter: Pigeon.FCM, - project_id: System.get_env("FCM_PROJECT"), - service_account_json: System.get_env("FCM_SERVICE_ACCOUNT_JSON") + auth: PigeonTest.Goth, + project_id: System.get_env("FCM_PROJECT") diff --git a/config/dialyzer.ignore-warnings b/config/dialyzer.ignore-warnings deleted file mode 100644 index 21f559f3..00000000 --- a/config/dialyzer.ignore-warnings +++ /dev/null @@ -1,2 +0,0 @@ -:0: Unknown function 'Elixir.Pigeon.Configurable. -:0: Unknown function 'Elixir.Pigeon.Encodable. diff --git a/docs/ADM Amazon Android.md b/docs/ADM Amazon Android.md deleted file mode 100644 index b2ef94e0..00000000 --- a/docs/ADM Amazon Android.md +++ /dev/null @@ -1,78 +0,0 @@ -# ADM (Amazon Android) - -## Usage - -1. Set your environment variables. - - ```elixir - config :pigeon, :adm, - client_id: "your_oauth2_client_id_here", - client_secret: "your_oauth2_client_secret_here" - ``` - -2. Create a notification packet. - - ```elixir - msg = %{ "body" => "your message" } - n = Pigeon.ADM.Notification.new("your device registration ID", msg) - ``` - -3. Send the packet. - - ```elixir - Pigeon.ADM.push(n) - ``` - -## Handling Push Responses - -1. Pass an optional anonymous function as your second parameter. - - ```elixir - data = %{ message: "your message" } - n = Pigeon.ADM.Notification.new("device registration ID", data) - Pigeon.ADM.push(n, on_response: fn(x) -> IO.inspect(x) end) - ``` - -2. Responses return a notification with an updated `:response` key. - You could handle responses like so: - - ```elixir - on_response_handler = fn(x) -> - case x.response do - :success -> - # Push successful - :ok - :update -> - new_reg_id = x.updated_registration_id - # Update the registration ID in the database - :invalid_registration_id -> - # Remove the bad ID from the database - :unregistered -> - # Remove the bad ID from the database - error -> - # Handle other errors - end - end - - data = %{ message: "your message" } - n = Pigeon.ADM.Notification.new("your registration id", data) - Pigeon.ADM.push(n, on_response: on_response_handler) - ``` - -## Error Responses - -_Taken from [Amazon Device Messaging docs](https://developer.amazon.com/public/apis/engage/device-messaging/tech-docs/06-sending-a-message)_ - -| Reason | Description | -| ---------------------------- | -------------------------------- | -| `:invalid_registration_id` | Invalid Registration Token | -| `:invalid_data` | Bad format JSON data | -| `:invalid_consolidation_key` | Invalid Consolidation Key | -| `:invalid_expiration` | Invalid expiresAfter value | -| `:invalid_checksum` | Invalid md5 value | -| `:invalid_type` | Invalid Type header | -| `:unregistered` | App instance no longer available | -| `:access_token_expired` | Expired OAuth access token | -| `:message_too_large` | Data size exceeds 6 KB | -| `:max_rate_exceeded` | See Retry-After response header | -| `:unknown_error` | Unknown Error | diff --git a/docs/APNS Apple iOS.md b/docs/APNS Apple iOS.md deleted file mode 100644 index 728f9885..00000000 --- a/docs/APNS Apple iOS.md +++ /dev/null @@ -1,238 +0,0 @@ -# APNS (Apple iOS) - -## Usage - -1. Set your environment variables. See below for setting up your certificate and key. - - ```elixir - config :pigeon, :apns, - apns_default: %{ - cert: "cert.pem", - key: "key_unencrypted.pem", - mode: :dev - } - ``` - - This config sets up a `default` socket connection to send to APNS servers. `cert` and `key` can be any of the following: - - - Static file path - - Full-text string of the file contents (useful for environment variables) - - `{:my_app, "certs/cert.pem"}` (indicates path relative to the `priv` folder of the given application) - - Alternatively, you can use token based authentication: - - ```elixir - config :pigeon, :apns, - apns_default: %{ - key: "AuthKey.p8", - key_identifier: "ABC1234567", - team_id: "DEF8901234", - mode: :dev - } - ``` - - - `:key` - Created and downloaded via your developer account. Like `:cert` this can be a file path, file contents string or tuple - - `:key_identifier` - The 10-character key identifier associated with `:key`, obtained from your developer account - - `:team_id` - Your 10-character Team ID, obtained from your developer account - -2. Create a notification packet. **Note: Your push topic is generally the app's bundle identifier.** - - ```elixir - n = Pigeon.APNS.Notification.new("your message", "your device token", "your push topic (optional)") - ``` - -3. Send the packet. Pushes are synchronous and return the notification with an - updated `:response` key. - - ```elixir - Pigeon.APNS.push(n) - ``` - -## Notification Struct - -The contents of `payload` is what will be received on the iOS device. If -updating this field directly, use strings for your keys. It is recommended -to use the convenience functions defined in _Notifications with Custom Data_. -`expiration` is a UNIX epoch date in seconds (UTC). Passing a value of -`0` expires the notification immediately and Apple will not attempt to -redeliver it. - -```elixir -%Pigeon.APNS.Notification{ - collapse_id: String.t() | nil, - device_token: String.t() | nil, - expiration: non_neg_integer | nil, - id: String.t() | nil, - payload: %{String.t() => String.t()}, - response: atom, - topic: String.t() | nil -} -``` - -## Generating Your Certificate and Key .pem - -1. In Keychain Access, right-click your push certificate and - select _"Export..."_ -2. Export the certificate as `cert.p12` -3. Click the dropdown arrow next to the certificate, right-click the private - key and select _"Export..."_ -4. Export the private key as `key.p12` -5. From a shell, convert the certificate. - - ``` - openssl pkcs12 -clcerts -nokeys -out cert.pem -in cert.p12 - ``` - -6. Convert the key. Be sure to set a PEM pass phrase here. The pass phrase must be 4 or more characters in length or this will not work. You will need that pass phrase added here in order to remove it in the next step. - - ``` - openssl pkcs12 -nocerts -out key.pem -in key.p12 - ``` - -7. Remove the PEM pass phrase from the key. - - ``` - openssl rsa -in key.pem -out key_unencrypted.pem - ``` - -8. `cert.pem` and `key_unencrypted.pem` can now be used as the cert and key - in `Pigeon.push`, respectively. Set them in your `config.exs` - -## Notifications with Custom Data - -Notifications can contain additional information in `payload`. (e.g. setting -badge counters or defining custom sounds) - -```elixir -import Pigeon.APNS.Notification -n = Pigeon.APNS.Notification.new("message", "device token", "push topic") -|> put_badge(5) -|> put_sound("default") -|> put_content_available -|> put_mutable_content -|> put_category("category") -|> put_interruption_level("time-sensitive") -``` - -Using a more complex `alert` dictionary? - -```elixir -n -|> put_alert(%{ - "title" => "alert title", - "body" => "alert body" -}) -``` - -Define custom payload data like so: - -```elixir -n -|> put_custom(%{"your-custom-key" => %{ - "custom-value" => 500 - }}) -``` - -## Custom Worker Connections - -Multiple APNS worker connections can be configured simultaneously. -Useful for supporting multiple apps and/or certificates at once. - -```elixir -config :pigeon, :apns, - default: %{ - cert: "cert.pem", - key: "key_unencrypted.pem", - mode: :dev - }, - custom_worker: %{ - cert: "another_cert.pem", - key: "another_key_unencrypted.pem", - mode: :prod - } -``` - -Send pushes with a `to` option in your second parameter. - -```elixir -n = Pigeon.APNS.Notification.new("message", "device token", "push topic") -Pigeon.APNS.push(n, to: :custom_worker) -``` - -You can also start connections manually. - -```elixir -iex> {:ok, pid} = Pigeon.APNS.start_connection(cert: "cert.pem", -...> key: "key.pem", mode: :dev) -iex> Pigeon.APNS.push(notif, to: pid) - -iex> Pigeon.APNS.start_connection(cert: "cert.pem", key: "key.pem", -...> mode: :dev, name: :custom) -iex> Pigeon.APNS.push(notif, to: :custom) -``` - -## Asynchronous Pushing - -1. Pass an `on_response` option with an anonymous function in your - second parameter. - - ```elixir - n = Pigeon.APNS.Notification.new("message", "device token", "push topic") - Pigeon.APNS.push(n, on_response: fn(x) -> IO.inspect(x) end) - ``` - -2. Responses return a notification with an updated `:response` key. - You could handle responses like so: - - ```elixir - handler = fn(%Pigeon.APNS.Notification{response: response}) -> - case response do - :success -> - Logger.debug "Push successful!" - :bad_device_token -> - Logger.error "Bad device token!" - _error -> - Logger.error "Some other error happened." - end - end - - n = Pigeon.APNS.Notification.new("message", "device token", "push topic") - Pigeon.APNS.push(n, on_response: handler) - ``` - -## Error Responses - -_Taken from [APNS Provider API](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html#//apple_ref/doc/uid/TP40008194-CH11-SW17)_ - -| Reason | Description | -| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `:bad_collapse_id` | The collapse identifier exceeds the maximum allowed size | -| `:bad_device_token` | The specified device token was bad. Verify that the request contains a valid token and that the token matches the environment. | -| `:bad_expiration_date` | The apns-expiration value is bad. | -| `:bad_message_id` | The apns-id value is bad. | -| `:bad_priority` | The apns-priority value is bad. | -| `:bad_topic` | The apns-topic was invalid. | -| `:device_token_not_for_topic` | The device token does not match the specified topic. | -| `:duplicate_headers` | One or more headers were repeated. | -| `:idle_timeout` | Idle time out. | -| `:missing_device_token` | The device token is not specified in the request :path. Verify that the :path header contains the device token. | -| `:missing_topic` | The apns-topic header of the request was not specified and was required. The apns-topic header is mandatory when the client is connected using a certificate that supports multiple topics. | -| `:payload_empty` | The message payload was empty. | -| `:topic_disallowed` | Pushing to this topic is not allowed. | -| `:bad_certificate` | The certificate was bad. | -| `:bad_certificate_environment` | The client certificate was for the wrong environment. | -| `:expired_provider_token` | The provider token is stale and a new token should be generated. | -| `:forbidden` | The specified action is not allowed. | -| `:invalid_provider_token` | The provider token is not valid or the token signature could not be verified. | -| `:invalid_push_type` | The apns-push-type value is invalid. | -| `:missing_provider_token` | No provider certificate was used to connect to APNs and Authorization header was missing or no provider token was specified. | -| `:bad_path` | The request contained a bad :path value. | -| `:method_not_allowed` | The specified :method was not POST. | -| `:expired_token` | The device token has expired. | -| `:unregistered` | The device token is inactive for the specified topic. | -| `:payload_too_large` | The message payload was too large. The maximum payload size is 4096 bytes. | -| `:too_many_provider_token_updates` | The provider token is being updated too often. | -| `:too_many_requests` | Too many requests were made consecutively to the same device token. | -| `:internal_server_error` | An internal server error occurred. | -| `:service_unavailable` | The service is unavailable. | -| `:shutdown` | The server is shutting down. | diff --git a/docs/FCM Android.md b/docs/FCM Android.md deleted file mode 100644 index 99fca4a3..00000000 --- a/docs/FCM Android.md +++ /dev/null @@ -1,125 +0,0 @@ -# FCM (Android) - -## Usage - -1. Set your environment variables. - - ```elixir - config :pigeon, :fcm, - fcm_default: %{ - key: "your_fcm_key_here" - } - ``` - -2. Create a notification packet. - - ```elixir - msg = %{"body" => "your message"} - n = Pigeon.FCM.Notification.new("your device registration ID", msg) - ``` - -3. Send the packet. Pushes are synchronous and return the notification with - updated `:status` and `:response` keys. If `:status` is success, `:response` - will contain a keyword list of individual registration ID responses. - - ```elixir - Pigeon.FCM.push(n) - ``` - -## Sending to Multiple Registration IDs - -Pass in a list of registration IDs, as many as you want. - -```elixir -msg = %{"body" => "your message"} -n = Pigeon.FCM.Notification.new(["first ID", "second ID"], msg) -``` - -## Notification Struct - -```elixir -%Pigeon.FCM.Notification{ - collapse_key: nil | String.t(), - dry_run: boolean, - message_id: nil | String.t(), - payload: %{...}, - priority: :normal | :high, - registration_id: String.t() | [String.t(), ...], - response: [] | [{atom, String.t()}, ...], | atom, - restricted_package_name: nil | String.t(), - status: atom | nil, - time_to_live: non_neg_integer -} -``` - -## Notifications with Custom Data - -FCM accepts both `notification` and `data` keys in its JSON payload. Set them like so: - -```elixir -notification = %{"body" => "your message"} -data = %{"key" => "value"} -Pigeon.FCM.Notification.new("registration ID", notification, data) -``` - -or - -```elixir -Pigeon.FCM.Notification.new("registration ID") -|> put_notification(%{"body" => "your message"}) -|> put_data(%{"key" => "value"}) -``` - -## Handling Push Responses - -1. Pass an optional anonymous function as your second parameter. - - ```elixir - data = %{message: "your message"} - n = Pigeon.FCM.Notification.new(data, "device registration ID") - Pigeon.FCM.push(n, fn(x) -> IO.inspect(x) end) - {:ok, %Pigeon.FCM.Notification{...}} - ``` - -2. Responses return the notification with an updated response. - - ```elixir - on_response = fn(n) -> - case n.status do - :success -> - bad_regids = FCM.Notification.remove?(n) - to_retry = FCM.Notification.retry?(n) - # Handle updated regids, remove bad ones, etc - :unauthorized -> - # Bad FCM key - error -> - # Some other error - end - end - - data = %{message: "your message"} - n = Pigeon.FCM.Notification.new("your device token", data) - Pigeon.FCM.push(n, on_response: on_response) - ``` - -## Error Responses - -_Slightly modified from [FCM Server Reference](https://firebase.google.com/docs/cloud-messaging/http-server-ref#error-codes)_ - -| Reason | Description | -| ------------------------------- | ---------------------------- | -| `:missing_registration` | Missing Registration Token | -| `:invalid_registration` | Invalid Registration Token | -| `:not_registered` | Unregistered Device | -| `:invalid_package_name` | Invalid Package Name | -| `:authentication_error` | Authentication Error | -| `:mismatch_sender_id` | Mismatched Sender | -| `:invalid_json` | Invalid JSON | -| `:message_too_big` | Message Too Big | -| `:invalid_data_key` | Invalid Data Key | -| `:invalid_ttl` | Invalid Time to Live | -| `:unavailable` | Timeout | -| `:internal_server_error` | Internal Server Error | -| `:device_message_rate_exceeded` | Message Rate Exceeded | -| `:topics_message_rate_exceeded` | Topics Message Rate Exceeded | -| `:unknown_error` | Unknown Error | diff --git a/docs/Migrating to v1-1-0.md b/docs/Migrating to v1-1-0.md deleted file mode 100644 index 2ed50a6e..00000000 --- a/docs/Migrating to v1-1-0.md +++ /dev/null @@ -1,34 +0,0 @@ -# Migrating to v1.1.0 - -## General - -- Kadabra has been bumped to `v0.3.1`. Minimum requirements are now Elixir - v1.4 / OTP 19.2 -- Pushes are now synchronous by default for all services. For async - functionality, pass an `:on_response` callback as an option with `push/2`. -- `{:ok, notif}`/`{:error, reason, notif}` response tuples have been replaced - with a `:reponse` key on the notification. - -## APNS - -- `APNS.Config.config/1` has been renamed to `APNS.Config.new/1` -- `[%APNS.Notification{}, ...]` is now returned instead of - `%{ok: [...], error: [...]}` on synchronous APNS pushes. -- `:use_2197` config option has been replaced with `:port`. APNS servers still - only accept `443` and `2197`, but other ports may be useful for test servers. -- `:reconnect` is now false by default -- `push/3` removed in favor of `push/2` with options - -## FCM - -- `FCM.NotificationResponse` has been dropped in favor of returning - `FCM.Notification` structs with updated response keys. The old - `NotificationResponse` keys`:ok`, `:remove`, - `:update` and `:retry` can be similarly accessed on `Notification` with the - helper functions `success?/1`, `remove?/1`, `update?/1` and `retry?/1`. -- `push/3` removed in favor of `push/2` with options - -## ADM - -- `ADM.Config.config/1` has been renamed to `ADM.Config.new/1` -- `push/3` removed in favor of `push/2` with options diff --git a/docs/Migrating-to-v2-0-0.md b/docs/Migrating-to-v2-0-0.md index 0711574c..20bf6823 100644 --- a/docs/Migrating-to-v2-0-0.md +++ b/docs/Migrating-to-v2-0-0.md @@ -11,8 +11,8 @@ no additional changes are required. ```elixir [ - {:pigeon, "~> 2.0"} # Change this - {:kadadbra, ~> 0.6.0} # Remove this + {:pigeon, "~> 2.0"}, # Change this + {:kadabra, "~> 0.6.0"} # Remove this ] ``` diff --git a/lib/pigeon/apns.ex b/lib/pigeon/apns.ex index 193d1863..38266d08 100644 --- a/lib/pigeon/apns.ex +++ b/lib/pigeon/apns.ex @@ -168,8 +168,6 @@ defmodule Pigeon.APNS do alias Pigeon.APNS.{ConfigParser, Error} alias Pigeon.HTTP.{Request, RequestQueue} - require Logger - @impl true def init(opts) do config = ConfigParser.parse(opts) @@ -232,6 +230,7 @@ defmodule Pigeon.APNS do Pigeon.HTTP.handle_info(msg, state, &handle_response/1) end + @doc false @spec handle_response(Request.t()) :: :ok def handle_response(%{status: 200} = request) do %{headers: headers, notification: notification} = request @@ -250,6 +249,7 @@ defmodule Pigeon.APNS do |> process_on_response() end + @doc false @spec get_header([{String.t(), String.t()}], String.t()) :: String.t() | nil def get_header(headers, key) do case Enum.find(headers, fn {k, _val} -> k == key end) do diff --git a/lib/pigeon/apns/error.ex b/lib/pigeon/apns/error.ex index ce9108d2..9adcc691 100644 --- a/lib/pigeon/apns/error.ex +++ b/lib/pigeon/apns/error.ex @@ -1,8 +1,6 @@ defmodule Pigeon.APNS.Error do @moduledoc false - require Logger - alias Pigeon.APNS.Notification @doc false diff --git a/lib/pigeon/apns/token.ex b/lib/pigeon/apns/token.ex index 0797c7d8..d07d1f48 100644 --- a/lib/pigeon/apns/token.ex +++ b/lib/pigeon/apns/token.ex @@ -4,8 +4,6 @@ defmodule Pigeon.APNS.Token do alias Pigeon.APNS.JWTConfig - require Logger - # seconds - 10 seconds short of one hour @token_max_age 3_590 diff --git a/lib/pigeon/configurable.ex b/lib/pigeon/configurable.ex index 6b9a6a25..701cb054 100644 --- a/lib/pigeon/configurable.ex +++ b/lib/pigeon/configurable.ex @@ -8,10 +8,6 @@ defprotocol Pigeon.Configurable do def push_payload(config, notification, opts) - # def handle_end_stream(config, stream, notification) - - # def handle_response(config, request) - @doc ~S""" Schedules connection ping if necessary. diff --git a/lib/pigeon/fcm.ex b/lib/pigeon/fcm.ex index 8bb3f75f..ace6a623 100644 --- a/lib/pigeon/fcm.ex +++ b/lib/pigeon/fcm.ex @@ -16,7 +16,7 @@ defmodule Pigeon.FCM do ### Install and configure Goth. - Install and configure [`goth`](https://hexdocs.pm/goth/1.4.3/readme.html#installation) + Install and configure [`goth`](https://hexdocs.pm/goth/readme.html#installation) if you haven't already. `Pigeon.FCM` requires it for token authentication. ### Configure your dispatcher. @@ -119,8 +119,6 @@ defmodule Pigeon.FCM do alias Pigeon.FCM.{Config, Error} alias Pigeon.HTTP.{Request, RequestQueue} - require Logger - @impl Pigeon.Adapter def init(opts) do config = Pigeon.FCM.Config.new(opts) @@ -183,6 +181,7 @@ defmodule Pigeon.FCM do Pigeon.HTTP.handle_info(msg, state, &handle_response/1) end + @doc false @spec handle_response(Request.t()) :: :ok def handle_response(%{body: body, notification: notif}) do body diff --git a/lib/pigeon/fcm/config.ex b/lib/pigeon/fcm/config.ex index c41243af..ea46558b 100644 --- a/lib/pigeon/fcm/config.ex +++ b/lib/pigeon/fcm/config.ex @@ -51,10 +51,7 @@ end defimpl Pigeon.Configurable, for: Pigeon.FCM.Config do @moduledoc false - import Pigeon.Tasks, only: [process_on_response: 1] - alias Pigeon.Encodable - alias Pigeon.FCM.Error # Configurable Callbacks @@ -107,24 +104,6 @@ defimpl Pigeon.Configurable, for: Pigeon.FCM.Config do Encodable.binary_payload(notification) end - def handle_end_stream(_config, %{error: nil} = stream, notif) do - stream.body - |> Pigeon.json_library().decode!() - |> case do - %{"name" => name} -> - notif - |> Map.put(:name, name) - |> Map.put(:response, :success) - |> process_on_response() - - %{"error" => error} -> - notif - |> Map.put(:error, error) - |> Map.put(:response, Error.parse(error)) - |> process_on_response() - end - end - def schedule_ping(_config), do: :ok def close(_config) do @@ -140,25 +119,14 @@ defimpl Pigeon.Configurable, for: Pigeon.FCM.Config do when not is_atom(mod) or is_nil(mod) do raise Pigeon.ConfigError, reason: "attempted to start without valid :auth module", - config: redact(config) + config: config end defp do_validate!({:project_id, value}, config) when not is_binary(value) do raise Pigeon.ConfigError, reason: "attempted to start without valid :project_id", - config: redact(config) + config: config end defp do_validate!({_key, _value}, _config), do: :ok - - @doc false - def redact(config) when is_map(config) do - [:service_account_json] - |> Enum.reduce(config, fn key, acc -> - case Map.get(acc, key) do - val when is_map(val) -> Map.put(acc, key, "[FILTERED]") - _ -> acc - end - end) - end end diff --git a/lib/pigeon/http.ex b/lib/pigeon/http.ex index e9d317d5..4fc6031a 100644 --- a/lib/pigeon/http.ex +++ b/lib/pigeon/http.ex @@ -20,9 +20,7 @@ defmodule Pigeon.HTTP do |> RequestQueue.process(queue) |> RequestQueue.pop_done() - for {_ref, request} <- done do - if request.notification, do: handler.(request) - end + Enum.each(done, &handle_done(&1, handler)) {:noreply, %{state | queue: queue, socket: socket}} @@ -31,4 +29,7 @@ defmodule Pigeon.HTTP do {:noreply, %{state | socket: socket}} end end + + defp handle_done({_ref, %{notification: nil}}, _handler), do: :ok + defp handle_done({_ref, request}, handler), do: handler.(request) end diff --git a/mix.exs b/mix.exs index 792dc067..1df77657 100644 --- a/mix.exs +++ b/mix.exs @@ -9,22 +9,15 @@ defmodule Pigeon.Mixfile do def project do [ app: :pigeon, - build_embedded: Mix.env() == :prod, deps: deps(), description: @description, dialyzer: dialyzer(), docs: docs(), - elixir: "~> 1.7", + elixir: "~> 1.16", elixirc_options: [warnings_as_errors: true], elixirc_paths: elixirc_paths(Mix.env()), name: @name, package: package(), - preferred_cli_env: [ - coveralls: :test, - "coveralls.detail": :test, - "coveralls.post": :test, - "coveralls.html": :test - ], source_url: @source_url, start_permanent: Mix.env() == :prod, test_coverage: [tool: ExCoveralls], @@ -32,6 +25,17 @@ defmodule Pigeon.Mixfile do ] end + def cli do + [ + preferred_envs: [ + coveralls: :test, + "coveralls.detail": :test, + "coveralls.html": :test, + "coveralls.post": :test + ] + ] + end + defp elixirc_paths(:dev), do: ["lib", "test/support"] defp elixirc_paths(:test), do: ["lib", "test/support"] defp elixirc_paths(_), do: ["lib"] @@ -47,7 +51,6 @@ defmodule Pigeon.Mixfile do [ {:credo, "~> 1.0", only: [:dev, :test], runtime: false}, {:dialyxir, "~> 1.0", only: [:dev], runtime: false}, - {:earmark, "~> 1.0", only: :dev, runtime: false}, {:excoveralls, "~> 0.5", only: :test, runtime: false}, {:ex_doc, ">= 0.0.0", only: :dev, runtime: false}, {:goth, "~> 1.4.3"}, diff --git a/mix.lock b/mix.lock index d8191401..03b5cdee 100644 --- a/mix.lock +++ b/mix.lock @@ -1,41 +1,25 @@ %{ "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, - "castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"}, - "certifi": {:hex, :certifi, "2.14.0", "ed3bef654e69cde5e6c022df8070a579a79e8ba2368a00acf3d75b82d9aceeed", [:rebar3], [], "hexpm", "ea59d87ef89da429b8e905264fdec3419f84f2215bb3d81e07a18aac919026c3"}, - "credo": {:hex, :credo, "1.7.12", "9e3c20463de4b5f3f23721527fcaf16722ec815e70ff6c60b86412c695d426c1", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8493d45c656c5427d9c729235b99d498bd133421f3e0a683e5c1b561471291e5"}, - "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"}, - "earmark": {:hex, :earmark, "1.4.47", "7e7596b84fe4ebeb8751e14cbaeaf4d7a0237708f2ce43630cfd9065551f94ca", [:mix], [], "hexpm", "3e96bebea2c2d95f3b346a7ff22285bc68a99fbabdad9b655aa9c6be06c698f8"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"}, - "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, - "ex_doc": {:hex, :ex_doc, "0.38.1", "bae0a0bd5b5925b1caef4987e3470902d072d03347114ffe03a55dbe206dd4c2", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "754636236d191b895e1e4de2ebb504c057fe1995fdfdd92e9d75c4b05633008b"}, + "credo": {:hex, :credo, "1.7.19", "cc52129665fc7c15143d47838fda0f9cd6dac9ceced7bf4da6f85fcbfe64b12a", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "2d8bc95d5a7bb99dd2613621d4f08c6a3575c3fd4b62e6a2b48a100352a557b8"}, + "dialyxir": {:hex, :dialyxir, "1.4.8", "7ef671a8aff9948b091d8c30f09467fbb16e77305cda451bce48109a0f5e021c", [:mix], [{:erlex, ">= 0.2.8", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "cbd5a851571e5dfeb32aaf2e840bfa98b7864cb3071bf2ef5d95d1276b12e072"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.46", "67607a0532e810c6f630a515c548d0b24949643f168cc556303bee4cf96105c7", [:mix], [], "hexpm", "9c44636e8a1c68c62f526b2dcd85d941dbbcee7ab82cf64ba06ce28bef8e89f5"}, + "erlex": {:hex, :erlex, "0.2.9", "7debbbaa9f4f368b8cd648983e0f1d7963028508e9c59e9d4ed504e94ef52a55", [:mix], [], "hexpm", "8cfffc0ec7159e6d73de2ab28a588064de80f88b2798d5cbe4482cbbc200178b"}, + "ex_doc": {:hex, :ex_doc, "0.40.4", "66f2e42bf588594d5a8aab31cad87f2ddad09d0da1b1a2f379340ec2c2e497cb", [:mix], [{:earmark_parser, "~> 1.4.46", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "6222b9e423d76584ee34df2c82a5ed72c2d53dc153f7f483ad28b378694186cc"}, "excoveralls": {:hex, :excoveralls, "0.18.5", "e229d0a65982613332ec30f07940038fe451a2e5b29bce2a5022165f0c9b157e", [:mix], [{:castore, "~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "523fe8a15603f86d64852aab2abe8ddbd78e68579c8525ae765facc5eae01562"}, - "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"}, - "finch": {:hex, :finch, "0.19.0", "c644641491ea854fc5c1bbaef36bfc764e3f08e7185e1f084e35e0672241b76d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "fc5324ce209125d1e2fa0fcd2634601c52a787aff1cd33ee833664a5af4ea2b6"}, - "gen_stage": {:hex, :gen_stage, "1.1.0", "dd0c0f8d2f3b993fdbd3d58e94abbe65380f4e78bdee3fa93d5618d7d14abe60", [:mix], [], "hexpm", "7f2b36a6d02f7ef2ba410733b540ec423af65ec9c99f3d1083da508aca3b9305"}, + "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, + "finch": {:hex, :finch, "0.23.0", "e3f9287ac25a8832f848b144c2b57346aac65b205e2e0629a52adfe6507fd837", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.8", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "80e58d3f936f57e3fdf404f83a3642897ae6d9fb642934e46da4d8fe761b99d5"}, "goth": {:hex, :goth, "1.4.5", "ee37f96e3519bdecd603f20e7f10c758287088b6d77c0147cd5ee68cf224aade", [:mix], [{:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:jose, "~> 1.11", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "0fc2dce5bd710651ed179053d0300ce3a5d36afbdde11e500d57f05f398d5ed5"}, - "hackney": {:hex, :hackney, "1.23.0", "55cc09077112bcb4a69e54be46ed9bc55537763a96cd4a80a221663a7eafd767", [:rebar3], [{:certifi, "~> 2.14.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "6cd1c04cd15c81e5a493f167b226a15f0938a84fc8f0736ebe4ddcab65c0b44e"}, - "hpack": {:hex, :hpack_erl, "0.3.0", "2461899cc4ab6a0ef8e970c1661c5fc6a52d3c25580bc6dd204f84ce94669926", [:rebar3], [], "hexpm", "d6137d7079169d8c485c6962dfe261af5b9ef60fbc557344511c1e65e3d95fb0"}, - "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, - "httpoison": {:hex, :httpoison, "2.2.3", "a599d4b34004cc60678999445da53b5e653630651d4da3d14675fedc9dd34bd6", [:mix], [{:hackney, "~> 1.21", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "fa0f2e3646d3762fdc73edb532104c8619c7636a6997d20af4003da6cfc53e53"}, - "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, - "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, - "joken": {:hex, :joken, "2.6.2", "5daaf82259ca603af4f0b065475099ada1b2b849ff140ccd37f4b6828ca6892a", [:mix], [{:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5134b5b0a6e37494e46dbf9e4dad53808e5e787904b7c73972651b51cce3d72b"}, - "jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"}, - "kadabra": {:hex, :kadabra, "0.6.1", "c28a7bc310e0dcb671ab13f181d653479157a7764c06c5d9778e3953ed0e3c61", [:mix], [{:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: false]}, {:hpack, "~> 0.3.0", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "a87f29709326ee9b9dfa78e52bf9e6e99bc375c528347ff1363aa6748061cf35"}, - "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "hpax": {:hex, :hpax, "1.0.4", "777de5d433b0fbdc7c418159c8055910faa8047ffdb3d6b31098d2a46cd7685c", [:mix], [], "hexpm", "afc7cb142ebcc2d01ce7816190b98ce5dd49e799111b24249f3443d730f377ca"}, + "jason": {:hex, :jason, "1.4.5", "2e3a008590b0b8d7388c20293e9dcc9cf3e5d642fd2a114e4cbbb52e595d940a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b0c823996102bcd0239b3c2444eb00409b72f6a140c1950bc8b457d836b30684"}, + "joken": {:hex, :joken, "2.7.0", "a9fd87805b1b58313435c04b950857d7557f019b157d503dc4783006e4e80ed1", [:mix], [{:jose, "~> 1.11.12", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "ffd0d92e12dbf497311386b75f9b5027d370e32c74f6e5576804d3a9eed668ab"}, + "jose": {:hex, :jose, "1.11.12", "06e62b467b61d3726cbc19e9b5489f7549c37993de846dfb3ee8259f9ed208b3", [:mix, :rebar3], [], "hexpm", "31e92b653e9210b696765cdd885437457de1add2a9011d92f8cf63e4641bab7b"}, + "makeup": {:hex, :makeup, "1.2.2", "882d46dc0905e9ff7abf2aab61a7e6b3dcc555533977d8a23b06019e6c89ac94", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "9a1a24e5b343b8ae16abea0822c10a6f75da27af7fa802ada5251f7579bfccfa"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, - "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, - "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.1.0", "835f7e60792e08824cda445639555d7bf1bbbddb1b60b306e33cb6f6db24dc74", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "1cd6780fb1dd1a03979abaed0fe82712b0625118fd5257d3ebbf73f960c73c3c"}, "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, - "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, - "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, + "mint": {:hex, :mint, "1.10.0", "85af3353bfc504f5bdfe494bd92b8490f87a306dc659ee1ad0af435107e898dc", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "8b16fb72aaa7531d206a1f05e4cc85509ba531ccec7a17a22736c9c95cbb24d1"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, - "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, - "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"}, - "req": {:hex, :req, "0.5.10", "a3a063eab8b7510785a467f03d30a8d95f66f5c3d9495be3474b61459c54376c", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "8a604815743f8a2d3b5de0659fa3137fa4b1cffd636ecb69b30b2b9b2c2559be"}, - "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, - "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, - "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, + "telemetry": {:hex, :telemetry, "1.4.2", "a0cb522801dffb1c49fe6e30561badffc7b6d0e180db1300df759faa22062855", [:rebar3], [], "hexpm", "928f6495066506077862c0d1646609eed891a4326bee3126ba54b60af61febb1"}, } diff --git a/test/pigeon/apns/error_test.exs b/test/pigeon/apns/error_test.exs index d12ddd88..4eb7bd6f 100644 --- a/test/pigeon/apns/error_test.exs +++ b/test/pigeon/apns/error_test.exs @@ -36,13 +36,13 @@ defmodule Pigeon.APNS.ErrorTest do ] for {actual, expected} <- reasons do - payload = JSON.encode!(%{"reason" => actual}) + payload = Jason.encode!(%{"reason" => actual}) assert Pigeon.APNS.Error.parse(payload) == expected end end test "parse/1 handles unknown error reasons" do - payload = JSON.encode!(%{"reason" => "UnknownReason"}) + payload = Jason.encode!(%{"reason" => "UnknownReason"}) assert Pigeon.APNS.Error.parse(payload) == :unknown_error end end diff --git a/test/pigeon/fcm_test.exs b/test/pigeon/fcm_test.exs index 116d762a..437799c9 100644 --- a/test/pigeon/fcm_test.exs +++ b/test/pigeon/fcm_test.exs @@ -5,7 +5,6 @@ defmodule Pigeon.FCMTest do doctest Pigeon.FCM.Notification, import: true alias Pigeon.FCM.Notification - require Logger @data %{"message" => "Test push"} @invalid_project_msg ~r/^attempted to start without valid :project_id/ From 4e302ded48e727822229974187733af256ce7244 Mon Sep 17 00:00:00 2001 From: Henry Popp Date: Mon, 7 Sep 2026 21:59:54 -0500 Subject: [PATCH 2/3] docs: CHANGELOG updates --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a96404fe..4745c8bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,13 +13,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 and `:kadabra`. ([#296](https://github.com/codedge-llc/pigeon/pull/296)) - Minimum supported Elixir version is now `1.16`. Pigeon supports every Elixir version that still receives security patches, and the minimum moves forward as Elixir drops - older branches. -- Support Elixir 1.20 and Erlang/OTP 29. + older branches. ([#310](https://github.com/codedge-llc/pigeon/pull/310)) +- Support Elixir 1.20 and Erlang/OTP 29. ([#310](https://github.com/codedge-llc/pigeon/pull/310)) **Removed** - Unpublished v1 guides in `docs/`. The `Pigeon.ADM`, `Pigeon.APNS`, and `Pigeon.FCM` - module docs cover v2 setup. The v1 guides remain on the `v1.6` branch. + module docs cover v2 setup. The v1 guides remain on the `v1.6` branch. ([#310](https://github.com/codedge-llc/pigeon/pull/310)) **Fixed** From 10d3787c4615f4a52620e135f95ad7445923edc5 Mon Sep 17 00:00:00 2001 From: Henry Popp Date: Mon, 7 Sep 2026 22:02:30 -0500 Subject: [PATCH 3/3] ci: add dialyzer job --- .github/workflows/ci.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9cb46462..1ade661c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,6 +43,35 @@ jobs: run: mix format --check-formatted - name: Run Credo run: mix credo + dialyzer: + name: Dialyzer + runs-on: ubuntu-latest + env: + ELIXIR_VERSION: "1.20" + OTP_VERSION: "29" + steps: + - uses: actions/checkout@v4 + - name: Set up Elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ env.ELIXIR_VERSION }} + otp-version: ${{ env.OTP_VERSION }} + - name: Restore dependencies cache + uses: actions/cache@v4 + with: + path: deps + key: ${{ runner.os }}-mix-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-mix- + - name: Restore PLT cache + uses: actions/cache@v4 + with: + path: priv/plts + key: ${{ runner.os }}-plt-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}-${{ hashFiles('**/mix.lock') }} + restore-keys: ${{ runner.os }}-plt-${{ env.ELIXIR_VERSION }}-${{ env.OTP_VERSION }}- + - name: Install dependencies + run: mix deps.get + - name: Run Dialyzer + run: mix dialyzer --format github test: name: Test (Elixir ${{ matrix.elixir }} / OTP ${{ matrix.otp }}) runs-on: ubuntu-latest