Packages
pigeon
1.5.0
2.0.1
2.0.0
2.0.0-rc.3
2.0.0-rc.2
2.0.0-rc.1
2.0.0-rc.0
1.6.3
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.4.0
1.3.2
1.3.1
1.3.0
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.1.6
1.1.5
1.1.4
1.1.3
1.1.2
1.1.1
1.1.0
1.1.0-rc.1
1.1.0-rc.0
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.13.1
0.13.0
0.12.1
0.12.0
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
0.9.1
0.9.0
0.8.0
0.7.0
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
iOS (APNS), Android (FCM), and Amazon Android (ADM) push notifications for Elixir.
Current section
57 Versions
Jump to
Current section
57 Versions
Compare versions
14
files changed
+187
additions
-75
deletions
| @@ -7,14 +7,13 @@ | |
| 7 7 | [](https://hex.pm/packages/pigeon) |
| 8 8 | |
| 9 9 | ## Installation |
| 10 | - *Requires Elixir 1.4/OTP 19.2 or later.* |
| 11 10 | |
| 12 11 | Add pigeon and kadabra as `mix.exs` dependencies: |
| 13 12 | |
| 14 13 | ```elixir |
| 15 14 | def deps do |
| 16 15 | [ |
| 17 | - {:pigeon, "~> 1.4.0"}, |
| 16 | + {:pigeon, "~> 1.5.0"}, |
| 18 17 | {:kadabra, "~> 0.4.4"} |
| 19 18 | ] |
| 20 19 | end |
| @@ -267,7 +266,7 @@ more complex. | |
| 267 266 | ) |
| 268 267 | end |
| 269 268 | |
| 270 | - defp build_config(%{"type: "fcm"} = config) do |
| 269 | + defp build_config(%{type: "fcm"} = config) do |
| 271 270 | Pigeon.FCM.Config.new( |
| 272 271 | name: Atom.to_string(config.name), |
| 273 272 | key: config.key |
| @@ -2,17 +2,18 @@ | |
| 2 2 | {<<"build_tools">>,[<<"mix">>]}. |
| 3 3 | {<<"description">>, |
| 4 4 | <<"HTTP2-compliant wrapper for sending iOS (APNS), Android (FCM),\nand Amazon Android (ADM) push notifications.">>}. |
| 5 | - {<<"elixir">>,<<"~> 1.4">>}. |
| 5 | + {<<"elixir">>,<<"~> 1.6">>}. |
| 6 6 | {<<"files">>, |
| 7 | - [<<"lib">>,<<"lib/pigeon">>,<<"lib/pigeon/configurable.ex">>, |
| 8 | - <<"lib/pigeon/encodable.ex">>,<<"lib/pigeon/connection">>, |
| 7 | + [<<"lib">>,<<"lib/pigeon">>,<<"lib/pigeon/config_error.ex">>, |
| 8 | + <<"lib/pigeon/configurable.ex">>,<<"lib/pigeon/encodable.ex">>, |
| 9 | + <<"lib/pigeon/connection">>, |
| 9 10 | <<"lib/pigeon/connection/notification_queue.ex">>, |
| 10 11 | <<"lib/pigeon/worker.ex">>,<<"lib/pigeon/apns">>, |
| 11 12 | <<"lib/pigeon/apns/notification.ex">>,<<"lib/pigeon/apns/config.ex">>, |
| 12 13 | <<"lib/pigeon/apns/error.ex">>,<<"lib/pigeon/apns/token.ex">>, |
| 13 14 | <<"lib/pigeon/apns/config_parser.ex">>,<<"lib/pigeon/apns/jwt_config.ex">>, |
| 14 | - <<"lib/pigeon/apns/shared.ex">>,<<"lib/pigeon/tasks.ex">>, |
| 15 | - <<"lib/pigeon/fcm.ex">>,<<"lib/pigeon/http2">>, |
| 15 | + <<"lib/pigeon/apns/shared.ex">>,<<"lib/pigeon/notification">>, |
| 16 | + <<"lib/pigeon/tasks.ex">>,<<"lib/pigeon/fcm.ex">>,<<"lib/pigeon/http2">>, |
| 16 17 | <<"lib/pigeon/http2/client.ex">>,<<"lib/pigeon/http2/stream.ex">>, |
| 17 18 | <<"lib/pigeon/http2/kadabra.ex">>,<<"lib/pigeon/adm">>, |
| 18 19 | <<"lib/pigeon/adm/notification.ex">>,<<"lib/pigeon/adm/worker.ex">>, |
| @@ -20,8 +21,8 @@ | |
| 20 21 | <<"lib/pigeon/fcm">>,<<"lib/pigeon/fcm/notification.ex">>, |
| 21 22 | <<"lib/pigeon/fcm/result_parser.ex">>,<<"lib/pigeon/fcm/config.ex">>, |
| 22 23 | <<"lib/pigeon/adm.ex">>,<<"lib/pigeon/connection.ex">>, |
| 23 | - <<"lib/pigeon/apns.ex">>,<<"lib/pigeon/exceptions.ex">>,<<"lib/pigeon.ex">>, |
| 24 | - <<"mix.exs">>,<<"README.md">>,<<"LICENSE">>]}. |
| 24 | + <<"lib/pigeon/apns.ex">>,<<"lib/pigeon.ex">>,<<"mix.exs">>,<<"README.md">>, |
| 25 | + <<"LICENSE">>]}. |
| 25 26 | {<<"licenses">>,[<<"MIT">>]}. |
| 26 27 | {<<"links">>,[{<<"GitHub">>,<<"https://github.com/codedge-llc/pigeon">>}]}. |
| 27 28 | {<<"name">>,<<"pigeon">>}. |
| @@ -30,7 +31,7 @@ | |
| 30 31 | {<<"name">>,<<"poison">>}, |
| 31 32 | {<<"optional">>,false}, |
| 32 33 | {<<"repository">>,<<"hexpm">>}, |
| 33 | - {<<"requirement">>,<<"~> 2.0 or ~> 3.0">>}], |
| 34 | + {<<"requirement">>,<<"~> 2.0 or ~> 3.0 or ~> 4.0">>}], |
| 34 35 | [{<<"app">>,<<"httpoison">>}, |
| 35 36 | {<<"name">>,<<"httpoison">>}, |
| 36 37 | {<<"optional">>,false}, |
| @@ -40,7 +41,7 @@ | |
| 40 41 | {<<"name">>,<<"gen_stage">>}, |
| 41 42 | {<<"optional">>,false}, |
| 42 43 | {<<"repository">>,<<"hexpm">>}, |
| 43 | - {<<"requirement">>,<<"~> 0.12">>}], |
| 44 | + {<<"requirement">>,<<"~> 0.12 or ~> 1.0">>}], |
| 44 45 | [{<<"app">>,<<"joken">>}, |
| 45 46 | {<<"name">>,<<"joken">>}, |
| 46 47 | {<<"optional">>,false}, |
| @@ -51,4 +52,4 @@ | |
| 51 52 | {<<"optional">>,true}, |
| 52 53 | {<<"repository">>,<<"hexpm">>}, |
| 53 54 | {<<"requirement">>,<<"~> 0.4.3">>}]]}. |
| 54 | - {<<"version">>,<<"1.4.0">>}. |
| 55 | + {<<"version">>,<<"1.5.0">>}. |
| @@ -55,4 +55,25 @@ defmodule Pigeon.ADM.Config do | |
| 55 55 | end |
| 56 56 | |
| 57 57 | defp valid_item?(item), do: is_binary(item) and String.length(item) > 0 |
| 58 | + |
| 59 | + @spec validate!(any) :: :ok |
| 60 | + def validate!(config) do |
| 61 | + if valid?(config) do |
| 62 | + :ok |
| 63 | + else |
| 64 | + raise Pigeon.ConfigError, |
| 65 | + reason: "attempted to start without valid client id and secret", |
| 66 | + config: redact(config) |
| 67 | + end |
| 68 | + end |
| 69 | + |
| 70 | + defp redact(config) do |
| 71 | + [:client_id, :client_secret] |
| 72 | + |> Enum.reduce(config, fn k, acc -> |
| 73 | + case Map.get(acc, k) do |
| 74 | + nil -> acc |
| 75 | + _ -> Map.put(acc, k, "[FILTERED]") |
| 76 | + end |
| 77 | + end) |
| 78 | + end |
| 58 79 | end |
| @@ -25,6 +25,7 @@ defmodule Pigeon.ADM.Worker do | |
| 25 25 | def init({:ok, config}), do: initialize_worker(config) |
| 26 26 | |
| 27 27 | def initialize_worker(config) do |
| 28 | + Pigeon.ADM.Config.validate!(config) |
| 28 29 | {:ok, |
| 29 30 | %{ |
| 30 31 | config: config, |
| @@ -96,12 +96,14 @@ defmodule Pigeon.APNS.Config do | |
| 96 96 | iex> Pigeon.APNS.Config.new( |
| 97 97 | ...> name: :test, |
| 98 98 | ...> mode: :prod, |
| 99 | - ...> cert: "test_cert.pem", |
| 100 | - ...> key: "test_key.pem", |
| 99 | + ...> cert: "test/support/test_cert.pem-mock", |
| 100 | + ...> key: "test/support/test_key.pem-mock", |
| 101 101 | ...> port: 2197, |
| 102 102 | ...> ping_period: 300_000 |
| 103 103 | ...> ) |
| 104 104 | %Pigeon.APNS.Config{uri: "api.push.apple.com", name: :test, |
| 105 | + certfile: Path.expand("test/support/test_cert.pem-mock"), |
| 106 | + keyfile: Path.expand("test/support/test_key.pem-mock"), |
| 105 107 | ping_period: 300000, port: 2197, reconnect: false} |
| 106 108 | |
| 107 109 | iex> config = Pigeon.APNS.Config.new(:apns_default) |
| @@ -114,17 +116,37 @@ defmodule Pigeon.APNS.Config do | |
| 114 116 | %__MODULE__{ |
| 115 117 | name: opts[:name], |
| 116 118 | reconnect: Keyword.get(opts, :reconnect, false), |
| 117 | - cert: ConfigParser.cert(opts[:cert]), |
| 119 | + cert: cert(opts[:cert]), |
| 118 120 | certfile: ConfigParser.file_path(opts[:cert]), |
| 119 | - key: ConfigParser.key(opts[:key]), |
| 121 | + key: key(opts[:key]), |
| 120 122 | keyfile: ConfigParser.file_path(opts[:key]), |
| 121 123 | uri: Keyword.get(opts, :uri, ConfigParser.uri_for_mode(opts[:mode])), |
| 122 124 | port: Keyword.get(opts, :port, 443), |
| 123 125 | ping_period: Keyword.get(opts, :ping_period, 600_000) |
| 124 126 | } |
| 127 | + |> ConfigParser.strip_errors(:cert, :certfile) |
| 128 | + |> ConfigParser.strip_errors(:key, :keyfile) |
| 125 129 | end |
| 126 130 | |
| 127 131 | def new(name) when is_atom(name), do: ConfigParser.parse(name) |
| 132 | + |
| 133 | + defp cert(bin) when is_binary(bin) do |
| 134 | + case :public_key.pem_decode(bin) do |
| 135 | + [{:Certificate, cert, _}] -> cert |
| 136 | + _ -> {:error, {:invalid, bin}} |
| 137 | + end |
| 138 | + end |
| 139 | + |
| 140 | + defp cert(other), do: {:error, {:invalid, other}} |
| 141 | + |
| 142 | + defp key(bin) when is_binary(bin) do |
| 143 | + case :public_key.pem_decode(bin) do |
| 144 | + [{:RSAPrivateKey, key, _}] -> {:RSAPrivateKey, key} |
| 145 | + _ -> {:error, {:invalid, bin}} |
| 146 | + end |
| 147 | + end |
| 148 | + |
| 149 | + defp key(other), do: {:error, {:invalid, other}} |
| 128 150 | end |
| 129 151 | |
| 130 152 | defimpl Pigeon.Configurable, for: Pigeon.APNS.Config do |
| @@ -164,12 +186,32 @@ defimpl Pigeon.Configurable, for: Pigeon.APNS.Config do | |
| 164 186 | |
| 165 187 | defdelegate close(config), to: Shared |
| 166 188 | |
| 167 | - def connect_socket_options(%{cert: nil, certfile: nil}) do |
| 168 | - {:error, :invalid_config} |
| 189 | + def validate!(config) do |
| 190 | + case config do |
| 191 | + %{cert: {:error, _}, certfile: {:error, _}} -> |
| 192 | + raise Pigeon.ConfigError, |
| 193 | + reason: "attempted to start without valid certificate", |
| 194 | + config: redact(config) |
| 195 | + |
| 196 | + %{key: {:error, _}, keyfile: {:error, _}} -> |
| 197 | + raise Pigeon.ConfigError, |
| 198 | + reason: "attempted to start without valid key", |
| 199 | + config: redact(config) |
| 200 | + |
| 201 | + _ -> |
| 202 | + :ok |
| 203 | + end |
| 169 204 | end |
| 170 205 | |
| 171 | - def connect_socket_options(%{key: nil, keyfile: nil}) do |
| 172 | - {:error, :invalid_config} |
| 206 | + defp redact(config) do |
| 207 | + [:cert, :key] |
| 208 | + |> Enum.reduce(config, fn key, acc -> |
| 209 | + case Map.get(acc, key) do |
| 210 | + bin when is_binary(bin) -> Map.put(acc, key, "[FILTERED]") |
| 211 | + {:RSAPrivateKey, _bin} -> Map.put(acc, key, "[FILTERED]") |
| 212 | + _ -> acc |
| 213 | + end |
| 214 | + end) |
| 173 215 | end |
| 174 216 | |
| 175 217 | def connect_socket_options(config) do |
Loading more files…