diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 55a07f2..d346e8b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,10 +29,10 @@ jobs: - 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') }} @@ -43,18 +43,55 @@ 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@v5 + - 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 + 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@v5 - 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 c133968..1e6eab3 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 2af47a1..39d4720 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,16 @@ 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. ([#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)) +- Prune stale `:httpoison` and `:kadabra` entries from `mix.lock`. ([#305](https://github.com/codedge-llc/pigeon/pull/305)) + +**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. ([#310](https://github.com/codedge-llc/pigeon/pull/310)) **Fixed** diff --git a/LICENSE b/LICENSE index 44daab0..fa3e9e7 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 694b531..2836e02 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 352d4af..76e4bba 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 21f559f..0000000 --- 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 b2ef94e..0000000 --- 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 728f988..0000000 --- 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 99fca4a..0000000 --- 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 2ed50a6..0000000 --- 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 0711574..20bf682 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 193d186..38266d0 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 ce9108d..9adcc69 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 0797c7d..d07d1f4 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 6b9a6a2..701cb05 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 8bb3f75..ace6a62 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 c41243a..ea46558 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/mix.exs b/mix.exs index 792dc06..1df7765 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 7c392e8..03b5cde 100644 --- a/mix.lock +++ b/mix.lock @@ -1,24 +1,23 @@ %{ "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "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.7", "dda948fcee52962e4b6c5b4b16b2d8fa7d50d8645bbae8b8685c3f9ecb7f5f4d", [:mix], [{:erlex, ">= 0.2.8", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b34527202e6eb8cee198efec110996c25c5898f43a4094df157f8d28f27d9efe"}, - "earmark": {:hex, :earmark, "1.4.49", "024521298c2308f0193ba7a5df742a5e901dad03cd44ba57f861d3f0fada5d1b", [:mix], [], "hexpm", "ffad0257b92ac342b236d1744f7f19793da77bbd7281f32ccdfb44c047a05bf2"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.45", "cba8369ab2a1342e419bc2760eec731b17be828941dcf494045d44766227e1d5", [:mix], [], "hexpm", "d3ec045bf122965db20c0bdb420e19ee1415843135327124918473feb4b328e8"}, + "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.3", "4a972ffe64bc07dc605af487e98fc19b72a4185f55ca031b94c0552d6071c1d9", [: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", "2756e357742fecd9749b489b85d67c9ce99c465f2e75728d9e6dc8d704b973de"}, + "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.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"}, - "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, + "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.6.2", "5daaf82259ca603af4f0b065475099ada1b2b849ff140ccd37f4b6828ca6892a", [:mix], [{:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5134b5b0a6e37494e46dbf9e4dad53808e5e787904b7c73972651b51cce3d72b"}, + "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.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, + "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.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"}, - "mint": {:hex, :mint, "1.9.0", "d6f534c2a3e98b2a8cc749b4796eb77e9e3af79a76f96e4c74035a827de0d318", [: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", "007154c7d8c43916aed3c93afd1f11aebbaa9c5ff4b7ba55ebe0d17ee0296042"}, + "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"}, diff --git a/test/pigeon/apns/error_test.exs b/test/pigeon/apns/error_test.exs index d12ddd8..4eb7bd6 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 116d762..437799c 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/