diff --git a/README.md b/README.md index f7eb16f3..c7fab1f9 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ notifications** to `FCM` (Firebase Cloud Messaging) and/or #### Running from DockerHub -We provide already built MongoosePush images. If you just want to use it, then all you need is `docker`, `FCM` app token and/or `APNS` app certificates. +We provide already built MongoosePush images. If you just want to use it, then all you need is `docker`, `FCM` app token and/or `APNS` app certificates and/or `Pushy` app token. In case of certificates you need to setup the following directory structure: * priv/ * ssl/ @@ -24,16 +24,23 @@ In case of certificates you need to setup the following directory structure: * dev_cert.pem - Development APNS app certificate * dev_key.pem - Development APNS app certificate's private key (has to be unencrypted) -Assuming that your `FCM` app token is "MY_FCM_SECRET_TOKEN" and you have the `priv` directory with all ceriticates in current directory, then you may start MongoosePush with the following command: +Assuming that your `FCM` app token is "MY_FCM_SECRET_TOKEN", `Pushy` app token is "MY_PUSHY_SECRET_TOKEN" and you have the `priv` directory with all certificates in current directory, then you may start MongoosePush with the following command: ```bash docker run -v `pwd`/priv:/opt/app/priv \ + -e PUSH_FCM_ENABLED=true \ -e PUSH_FCM_APP_KEY="MY_FCM_SECRET_TOKEN" \ + -e PUSH_PUSHY_ENABLED=true \ + -e PUSH_PUSHY_APP_KEY="MY_PUSHY_SECRET_TOKEN" \ + -e PUSH_APNS_ENABLED=true \ -e PUSH_HTTPS_CERTFILE="/opt/app/priv/ssl/rest_cert.pem" \ -e PUSH_HTTPS_KEYFILE="/opt/app/priv/ssl/rest_key.pem" \ -it --rm mongooseim/mongoose-push:latest ``` +Please note that each push service that is being used has to be enabled by `PUSH_FCM_ENABLED` / `PUSH_APNS_ENABLED` / `PUSH_PUSHY_ENABLED` as +all services are disabled by default. In the example above, we enable all push services, but you may want to skip some of them. + #### Building Building docker is really easy, just type: @@ -64,14 +71,20 @@ Environmental variables to configure production release: ##### General settings: * `PUSH_LOGLEVEL` - `debug`/`info`/`warn`/`error` - Log level of the application. `info` is the default one -* `PUSH_FCM_ENABLED` - `true`/`false` - Enable or disable `FCM` support. Enabled by default -* `PUSH_APNS_ENABLED` - `true`/`false` - Enable or disable `APNS` support. Enabled by default +* `PUSH_FCM_ENABLED` - `true`/`false` - Enable or disable `FCM` support. Disabled by default +* `PUSH_APNS_ENABLED` - `true`/`false` - Enable or disable `APNS` support. Disabled by default +* `PUSH_PUSHY_ENABLED` - `true`/`false` - Enable or disable `Pushy` support. Disabled by default ##### Settings for FCM service: * `PUSH_FCM_ENDPOINT` - Hostname of `FCM` service. Set only for local testing. By default this option points to the Google's official hostname * `PUSH_FCM_APP_KEY` - App key token to use with `FCM` service * `PUSH_FCM_POOL_SIZE` - Connection pool size for `FCM` service +##### Settings for Pushy service: +* `PUSH_PUSHY_ENDPOINT` - Hostname of `Pushy` service. Set only for local testing. By default this option points to the Google's official hostname +* `PUSH_PUSHY_APP_KEY` - App key token to use with `Pushy` service +* `PUSH_PUSHY_POOL_SIZE` - Connection pool size for `Pushy` service. Please note that `Pushy` uses HTTP 1.1, so the pool size has be be significantly bigger then in case of other services that run on HTTP/2 + ##### Settings for development APNS service: * `PUSH_APNS_DEV_ENDPOINT` - Hostname of `APNS` service. Set only for local testing. By default this option points to the Apple's official hostname * `PUSH_APNS_DEV_CERT` - Path Apple's development certfile used to communicate with `APNS` @@ -159,6 +172,28 @@ Each `FCM` pool may be configured by setting the following fields: You may entirely skip the `FCM` config entry to disable `FCM` support. +### Pushy configuration +Lets take a look at sample `Pushy` service configuration: +```elixir +config :mongoose_push, pushy: [ + default: [ + key: "fake_app_key", + pool_size: 50, + mode: :prod + ] + ] +``` + +This is a definition of a pool - each pool has a name and configuration. It is possible to have multiple named pools with different configuration, which includes pool size, environment mode etc. Currently the only reason you may want to do this, is to create separate production and development pools which may be selected by an `HTTP` client by specifying matching `:mode` in their push request. + +Each `Pushy` pool may be configured by setting the following fields: +* **key** (*required*) - you `Pushy` Server Key +* **pool_size** (*required*) - maximum number of used `HTTP 1.1` connections to Pushy service +* **mode** (*either `:prod` or `:dev`*) - pool's mode. The `HTTP` client may select pool used to push a notification by specifying matching option in the request +* **endpoint** (*optional*) - URL override for `Pushy` service. Useful mainly in tests + +You may entirely skip the `Pushy` config entry to disable `Pushy` support. + ### APNS configuration Lets take a look at sample `APNS` service configuration: diff --git a/config/config.exs b/config/config.exs index b80b04a2..d40efb3f 100644 --- a/config/config.exs +++ b/config/config.exs @@ -29,6 +29,29 @@ config :plug, :statuses, %{ 460 => "Invalid device token" } +lager_formater_config = [:date, 'T', :time, :color, ' [', :severity, '] ', :pid, ' ', :message, '\e[0m\r\n'] +config :lager, + colored: true, + handlers: [ + lager_console_backend: [ + level: :info, + formatter: :lager_default_formatter, + formatter_config: lager_formater_config + ], + lager_file_backend: [ + file: 'log/error.log', + level: :error, + formatter: :lager_default_formatter, + formatter_config: lager_formater_config + ], + lager_file_backend: [ + file: 'log/console.log', + level: :info, + formatter: :lager_default_formatter, + formatter_config: lager_formater_config + ] + ] + import_config "#{Mix.env}.exs" # Globally disable maru's "test mode". If we don't disable it explicitly diff --git a/config/prod.exs b/config/prod.exs index c90efaf4..d192f205 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -17,11 +17,15 @@ config :maru, MongoosePush.Router, config :mongoose_push, loglevel: {:system, :atom, "PUSH_LOGLEVEL", :info} + +config :mongoose_push, pushy_enabled: + {:system, :boolean, "PUSH_PUSHY_ENABLED", false} + config :mongoose_push, fcm_enabled: - {:system, :boolean, "PUSH_FCM_ENABLED", true} + {:system, :boolean, "PUSH_FCM_ENABLED", false} config :mongoose_push, apns_enabled: - {:system, :boolean, "PUSH_APNS_ENABLED", true} + {:system, :boolean, "PUSH_APNS_ENABLED", false} config :mongoose_push, fcm: [ default: [ @@ -32,6 +36,15 @@ config :mongoose_push, fcm: [ ] ] +config :mongoose_push, pushy: [ + default: [ + endpoint: {:system, :string, "PUSH_PUSHY_ENDPOINT", nil}, + key: {:system, :string, "PUSH_PUSHY_APP_KEY", "fake_app_key"}, + pool_size: {:system, :integer, "PUSH_PUSHY_POOL_SIZE", 100}, + mode: :prod, + ] +] + config :mongoose_push, apns: [ dev: [ endpoint: {:system, :string, "PUSH_APNS_DEV_ENDPOINT", nil}, diff --git a/lib/mongoose_push/api.ex b/lib/mongoose_push/api.ex index d713203e..7cc9b7a6 100644 --- a/lib/mongoose_push/api.ex +++ b/lib/mongoose_push/api.ex @@ -16,6 +16,9 @@ defmodule MongoosePush.API do {500, %{:details => reason}} end end + def to_status({:error, reason}) when is_binary(reason) do + {400, reason} + end def to_status({:error, _reason}) do {500, nil} end diff --git a/lib/mongoose_push/api/v1.ex b/lib/mongoose_push/api/v1.ex index bbbdad40..95b9c0e9 100644 --- a/lib/mongoose_push/api/v1.ex +++ b/lib/mongoose_push/api/v1.ex @@ -10,7 +10,7 @@ defmodule MongoosePush.API.V1 do parsers: [:urlencoded, :json, :multipart] params do - requires :service, type: Atom, values: [:fcm, :apns] + requires :service, type: Atom, values: [:fcm, :apns, :pushy] requires :body, type: String requires :title, type: String optional :badge, type: Integer diff --git a/lib/mongoose_push/api/v2.ex b/lib/mongoose_push/api/v2.ex index bd1f8c25..418a1625 100644 --- a/lib/mongoose_push/api/v2.ex +++ b/lib/mongoose_push/api/v2.ex @@ -10,7 +10,7 @@ defmodule MongoosePush.API.V2 do parsers: [:urlencoded, :json, :multipart] params do - requires :service, type: Atom, values: [:fcm, :apns] + requires :service, type: Atom, values: [:fcm, :apns, :pushy] optional :mode, type: Atom, values: [:prod, :dev] optional :priority, type: Atom, values: [:normal, :high] optional :time_to_live, type: Integer diff --git a/lib/mongoose_push/application.ex b/lib/mongoose_push/application.ex index b6eeef55..93077bf0 100644 --- a/lib/mongoose_push/application.ex +++ b/lib/mongoose_push/application.ex @@ -43,7 +43,8 @@ defmodule MongoosePush.Application do def services do [ fcm: MongoosePush.Service.FCM, - apns: MongoosePush.Service.APNS + apns: MongoosePush.Service.APNS, + pushy: MongoosePush.Service.Pushy ] end @@ -68,7 +69,7 @@ defmodule MongoosePush.Application do case service do :apns -> [:cert, :key] - :fcm -> + _ -> [] end config diff --git a/lib/mongoose_push/pools.ex b/lib/mongoose_push/pools.ex index c41d37cc..b1a1b347 100644 --- a/lib/mongoose_push/pools.ex +++ b/lib/mongoose_push/pools.ex @@ -24,6 +24,12 @@ defmodule MongoosePush.Pools do Enum.map(config, &(elem(&1, 0))) end + @spec pools_by_mode(MongoosePush.service, MongoosePush.mode) :: list(atom) + def pools_by_mode(:pushy = service, _mode) do + config = pools_config(service) + Enum.map(config, &(elem(&1, 0))) + end + def pools_by_mode(:apns = service, mode) do config = pools_config(service) diff --git a/lib/mongoose_push/service/apns.ex b/lib/mongoose_push/service/apns.ex index 066bc1c8..fabd6b85 100644 --- a/lib/mongoose_push/service/apns.ex +++ b/lib/mongoose_push/service/apns.ex @@ -74,7 +74,7 @@ defmodule MongoosePush.Service.APNS do :dev -> :development_endpoint :prod -> :production_endpoint end - Keyword.merge([{new_key, config[:endpoint]}], config) + Keyword.put(config, new_key, config[:endpoint]) end defp announce_subject(config) do @@ -101,7 +101,7 @@ defmodule MongoosePush.Service.APNS do all_topics = Certificate.extract_topics!(config[:cert]) default_topic = all_topics[:topic] Logger.info(~s"Successfully extracted default APNS topic: #{default_topic}") - Keyword.merge([default_topic: default_topic], config) + Keyword.put(config, :default_topic, default_topic) default_topic -> Logger.info(~s"Using user-defined default APNS topic: #{default_topic}") config diff --git a/lib/mongoose_push/service/fcm.ex b/lib/mongoose_push/service/fcm.ex index bec9b7f5..0e9f58c5 100644 --- a/lib/mongoose_push/service/fcm.ex +++ b/lib/mongoose_push/service/fcm.ex @@ -26,6 +26,8 @@ defmodule MongoosePush.Service.FCM do |> Enum.reduce(%{}, fn(field, map) -> Map.put(map, field, alert[field]) end) + |> Enum.filter(fn({_, value}) -> value != nil end) + |> Map.new() Notification.new(device_id, msg, request[:data]) |> Notification.put_priority(@priority_mapping[request[:priority]]) diff --git a/lib/mongoose_push/service/pushy.ex b/lib/mongoose_push/service/pushy.ex new file mode 100644 index 00000000..a3d95d61 --- /dev/null +++ b/lib/mongoose_push/service/pushy.ex @@ -0,0 +1,45 @@ +defmodule MongoosePush.Service.Pushy do + @moduledoc """ + Pushy service provider implementation. + """ + + @behaviour MongoosePush.Service + alias MongoosePush.Pools + require Logger + + @spec prepare_notification(String.t(), MongoosePush.request) :: + Service.notification + def prepare_notification(device_id, request) do + MongoosePush.Service.FCM.prepare_notification(device_id, request) + end + + @spec push(Service.notification(), String.t(), atom(), Service.options()) :: + :ok | {:error, term} + def push(notification, device_id, worker, opts \\ []) do + MongoosePush.Service.FCM.push(notification, device_id, worker, opts) + end + + @spec workers({atom, Keyword.t()} | nil) :: list(Supervisor.Spec.spec()) + def workers(nil), do: [] + def workers({pool_name, pool_config}) do + Logger.info ~s"Starting Pushy pool with API key #{filter_secret(pool_config[:key])}" + pool_size = pool_config[:pool_size] + Enum.map(1..pool_size, fn(id) -> + worker_name = Pools.worker_name(:pushy, pool_name, id) + Supervisor.Spec.worker(Pigeon.PushyWorker, + [worker_name, pool_config], [id: worker_name]) + end) + end + + defp filter_secret(secret) when is_binary(secret) do + prefix = String.slice(secret, 0..2) + suffix = + secret + |> String.slice(3..-1) + |> String.slice(-3..-1) + + prefix <> "*******" <> suffix + end + defp filter_secret(secret), do: secret + +end diff --git a/mix.exs b/mix.exs index 4bcdab9c..3fb38d1e 100644 --- a/mix.exs +++ b/mix.exs @@ -25,8 +25,8 @@ defmodule MongoosePush.Mixfile do defp deps do [ - {:chatterbox, github: "joedevivo/chatterbox", ref: "ff0c2e054430d2990b588afa6fb8f2d184dfeaea", override: true}, - {:pigeon, github: "rslota/pigeon", ref: "2860eee35b58e2d8674f805f1151f57b9faeca21"}, + {:pigeon, github: "rslota/pigeon", ref: "3c83127f25a638b0dd0324742daeb282defd5dc7"}, + {:chatterbox, github: "joedevivo/chatterbox", ref: "ff0c2e0", override: true}, {:maru, github: "rslota/maru", ref: "54fc038", override: true}, {:cowboy, "~> 2.3", override: true}, @@ -38,7 +38,7 @@ defmodule MongoosePush.Mixfile do {:confex, "~> 3.2", override: true}, {:mix_docker, "~> 0.5"}, {:uuid, "~> 1.1"}, - {:lager, ">= 3.6.9", override: true}, + {:lager, ">= 3.7.0", override: true}, {:logger_lager_backend, "~> 0.1.0"}, # Just overrides to make elixometer compile... @@ -51,7 +51,7 @@ defmodule MongoosePush.Mixfile do # Until eproxus/meck #fcc551e3 is in a release, we need to use master version # to include this commit (fixes mocking in Erlang 20.x + Elixir 1.5.x) {:meck, github: "eproxus/meck", override: true}, - {:httpoison, "~> 0.13"}, + {:httpoison, "~> 1.4"}, {:excoveralls, "~> 0.7", only: :test}, {:dialyxir, "~> 0.4", only: [:dev, :test], runtime: false}, {:credo, "~> 0.5", only: [:dev, :test]}, @@ -79,7 +79,7 @@ defmodule MongoosePush.Mixfile do end defp preferred_cli_env do - ["coveralls": :test, "coveralls.detail": :test, + [coveralls: :test, "coveralls.detail": :test, "coveralls.travis": :test, "coveralls.html": :test] end diff --git a/mix.lock b/mix.lock index 6c1f0b0c..c1da0fda 100644 --- a/mix.lock +++ b/mix.lock @@ -1,15 +1,15 @@ %{ - "artificery": {:hex, :artificery, "0.4.1", "90b1fcedb9034853b36dbea2174f9fc1172bd50b0686a723178103def5c9c1c8", [:mix], [], "hexpm"}, + "artificery": {:hex, :artificery, "0.4.2", "3ded6e29e13113af52811c72f414d1e88f711410cac1b619ab3a2666bbd7efd4", [:mix], [], "hexpm"}, "bear": {:hex, :bear, "0.8.5", "e95fca1627cd9e15baf93ce0a52aff16917baf325f0ee65b88cd715376cd2344", [:rebar3], [], "hexpm"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, "certifi": {:hex, :certifi, "2.0.0", "a0c0e475107135f76b8c1d5bc7efb33cd3815cb3cf3dea7aefdd174dabead064", [:rebar3], [], "hexpm"}, - "chatterbox": {:git, "https://github.com/joedevivo/chatterbox.git", "ff0c2e054430d2990b588afa6fb8f2d184dfeaea", [ref: "ff0c2e054430d2990b588afa6fb8f2d184dfeaea"]}, + "chatterbox": {:git, "https://github.com/joedevivo/chatterbox.git", "ff0c2e054430d2990b588afa6fb8f2d184dfeaea", [ref: "ff0c2e0"]}, "confex": {:hex, :confex, "3.3.1", "8febaf751bf293a16a1ed2cbd258459cdcc7ca53cfa61d3f83d49dd276a992b4", [:mix], [], "hexpm"}, "cowboy": {:hex, :cowboy, "2.3.0", "268429481d6f41ad5564ade4a2439c284b569083fca0d19078ad4f56b4624724", [:rebar3], [{:cowlib, "~> 2.2.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.4.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, "cowlib": {:hex, :cowlib, "2.2.1", "1afa5b233cee8f642c153ec6f1dc48db0dd9a43e2114bc8b43e9d59636f6ae1f", [:rebar3], [], "hexpm"}, "credo": {:hex, :credo, "0.9.3", "76fa3e9e497ab282e0cf64b98a624aa11da702854c52c82db1bf24e54ab7c97a", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, "dialyxir": {:hex, :dialyxir, "0.5.1", "b331b091720fd93e878137add264bac4f644e1ddae07a70bf7062c7862c4b952", [:mix], [], "hexpm"}, - "distillery": {:hex, :distillery, "2.0.12", "6e78fe042df82610ac3fa50bd7d2d8190ad287d120d3cd1682d83a44e8b34dfb", [:mix], [{:artificery, "~> 0.2", [hex: :artificery, repo: "hexpm", optional: false]}], "hexpm"}, + "distillery": {:hex, :distillery, "2.0.14", "25fc1cdad06282334dbf4a11b6e869cc002855c4e11825157498491df2eed594", [:mix], [{:artificery, "~> 0.2", [hex: :artificery, repo: "hexpm", optional: false]}], "hexpm"}, "earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [:mix], [], "hexpm"}, "elixometer": {:git, "https://github.com/esl/elixometer.git", "6678c97f64ac8a4cf7ecbf32a4d342576d466b69", []}, "ex_doc": {:hex, :ex_doc, "0.18.3", "f4b0e4a2ec6f333dccf761838a4b253d75e11f714b85ae271c9ae361367897b7", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}], "hexpm"}, @@ -21,12 +21,12 @@ "goldrush": {:hex, :goldrush, "0.1.9", "f06e5d5f1277da5c413e84d5a2924174182fb108dabb39d5ec548b27424cd106", [:rebar3], [], "hexpm"}, "hackney": {:hex, :hackney, "1.11.0", "4951ee019df102492dabba66a09e305f61919a8a183a7860236c0fde586134b6", [:rebar3], [{:certifi, "2.0.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "5.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, "hpack": {:git, "https://github.com/joedevivo/hpack.git", "6b58b6231e9b6ab83096715120578976f72f4f7c", [tag: "0.2.3"]}, - "httpoison": {:hex, :httpoison, "0.13.0", "bfaf44d9f133a6599886720f3937a7699466d23bb0cd7a88b6ba011f53c6f562", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, + "httpoison": {:hex, :httpoison, "1.5.1", "0f55b5b673b03c5c327dac7015a67cb571b99b631acc0bc1b0b98dcd6b9f2104", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "hut": {:hex, :hut, "1.2.0", "0089df0faa2827c605bbada88153f24fff5ea7a4be32ecf0250a7fdc2719cafb", [:"erlang.mk", :rebar, :rebar3], [], "hexpm"}, "idna": {:hex, :idna, "5.1.0", "d72b4effeb324ad5da3cab1767cb16b17939004e789d8c0ad5b70f3cea20c89a", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, "jason": {:hex, :jason, "1.0.0", "0f7cfa9bdb23fed721ec05419bcee2b2c21a77e926bce0deda029b5adc716fe2", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, "jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm"}, - "lager": {:hex, :lager, "3.6.9", "387bcd836dc0c8ad9c6d90a0e0ce5b29676847950cbc527bccc194a02028de8e", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, repo: "hexpm", optional: false]}], "hexpm"}, + "lager": {:hex, :lager, "3.7.0", "563ab17cd32134a3dd17ec3b3622e6d8f827506aa4f8c489158879bed87d980b", [:rebar3], [{:goldrush, "0.1.9", [hex: :goldrush, repo: "hexpm", optional: false]}], "hexpm"}, "logger_lager_backend": {:hex, :logger_lager_backend, "0.1.0", "4858d5ac26a3a6085274933bf8b3061973cadac4adbe8e0181933e8cece78abb", [:mix], [{:lager, "~> 3.2", [hex: :lager, repo: "hexpm", optional: false]}], "hexpm"}, "maru": {:git, "https://github.com/rslota/maru.git", "54fc0380cd4b7c8b597429d1fa70c2f08a714c01", [ref: "54fc038"]}, "maru_swagger": {:git, "https://github.com/elixir-maru/maru_swagger.git", "e3dfad2bd01d50566c041dee3fcb4af375918244", []}, @@ -37,7 +37,7 @@ "mix_docker": {:hex, :mix_docker, "0.5.0", "c7ad34008c43d4a949d69721f39c4d2a2afc509c179926a683117ea8dff8af59", [:mix], [{:distillery, "~> 1.2", [hex: :distillery, repo: "hexpm", optional: false]}], "hexpm"}, "mock": {:hex, :mock, "0.3.1", "994f00150f79a0ea50dc9d86134cd9ebd0d177ad60bd04d1e46336cdfdb98ff9", [:mix], [{:meck, "~> 0.8.8", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm"}, "parse_trans": {:hex, :parse_trans, "3.1.0", "1bad3b959941cc53ffd6f4769a5d2666f9cdf179b2d62826636497d3fbad9ec0", [:rebar3], [], "hexpm"}, - "pigeon": {:git, "https://github.com/rslota/pigeon.git", "2860eee35b58e2d8674f805f1151f57b9faeca21", [ref: "2860eee35b58e2d8674f805f1151f57b9faeca21"]}, + "pigeon": {:git, "https://github.com/rslota/pigeon.git", "fa7fa5e8686477c1ce19a7592bf4f13d994bc0f2", [ref: "fa7fa5e"]}, "plug": {:hex, :plug, "1.5.0", "224b25b4039bedc1eac149fb52ed456770b9678bbf0349cdd810460e1e09195b", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.1", [hex: :cowboy, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm"}, "pobox": {:hex, :pobox, "1.0.4", "e695bc3b2b547dd6c8e5a19cb743396e6670e306ad3ff498844738f9418bd686", [:rebar3], [], "hexpm"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},