This lib is intended to allow you to write an APNs provider for Apple Push Notificaion services (APNs) over HTTP2 in Erlang.
Copyright (c) 2017 Erlang Solutions Ltd. support@inaka.net, released under the Apache 2 license
Note: Currently it supports push notifications with certificate and authentication token.
BINARY_API: This lib supports http2. Check using Legacy Binary API
- You must have installed an updated Openssl version or, at least, be sure it supports TLS 1.2+. New APNs server only supports connections over TLS 1.2+.
- Erlang R21
apns_topic is app bundle identifier
voip notifications append '.voip' > !apple! undocumented
https://stackoverflow.com/questions/40301655/voip-push-with-new-token-based-auth
apns_erlang
user erlang.mk
as make system. To compile
$ make
To generate release
$ make rel
You can use apns_erlang
as a dependency in your rebar.config:
{deps , [
{apns, ".*", {git, "https://github.com/pankajsoni19/apns_erlang", {tag, "2.8.0"}}}
]}.
- copy the conf/sys.config.example to conf/sys.config
- Setup the necessary values
make rel
will create a release under_rel/apns
directory.
$ cd _rel/apns
$ bin/apns console
Checkout the sample config file in config/ directory.
You can find more info here
The other way is send all that info as a parameter to apns:connect/1
function encapsulated in a apns_connection:connection()
structure:
#{ name := name()
, env := 'development' | 'production'
`Refer Connection Options`
`Refer Other Options`
`Refer Feedback Option`
}.
-
name
is specified, the connection process is registered byname
. -
env
defines which apple host to connect to. Defaultenv
isproduction
#{ token_keyfile => path()
, token_kid => binary()
, team_id => binary()
}.
-
token_keyfile
is a path to the Authentication Key provided by Apple. This is in.p8
format and it doesn't need conversion. -
token_kid
is the token key file id when generated. It is the last string after_
in authtoken_keyfile
file name. Ex:APNsAuthKey_KEYID12345.p8
,token_kid
isKEYID12345
. -
team_id
is Apple developer TEAM id as on apple developer console.
Library maintains token signing and hourly renewal of signature key.
#{ certfile => path()
, keyfile => path()
}.
Supply cert paths in certfile
and keyfile
.
We need certfile
and keyfile
in .pem
format, here is an example of how to convert them, check the certificates section.
Alternatively, you can supply a cert binary in certdata
and a keydata
#{ certdata => binary()
, keydata => keydata()
}.
In above connection option following config variables can be specified.
#{ apple_port => integer()
, timeout => integer()
`Refer Reconnection Option`
`Refer Header Options`
}.
apple_port
https port to use. Default is 443.timeout
http body receive timeout in milliseconds. Default is 5000.
#{ feedback => { Module, Function } }.
Only invalid token causes the callback function to be called. Feedback function can be specified in the sys.config file.
It takes the following form.
M:F(ConnectionName, ApnsId | DeviceId, InValidationTimestamp).
-
ConnectionName
is apns connection registeration name. -
ApnsId
is apns_id specified in headers.DeviceId
is apple token. While callingpush_notification/4
if headers containapns_id
then callback will contain the same else library maintainsapns_id
toDeviceId
and returns the same in callback. -
InValidationTimestamp
in UTC seconds when apple device token was last valid.
After defining config options as above.
Example:
1> apns:connect(cert, my_first_connection).
{ok,<0.87.0>}
2> apns:connect(#{name => another_cert, env => production,
certfile => "priv/cert.pem", keyfile => "priv/key.pem"}).
3> apns:connect(token, my_second_connection).
{ok,<0.95.0>}
apns:connect/2
returns the connection pid
.
In some scenarios we don't want to assign names to the connections instead we want working just with the pid
(working with a pool of connections for example). If that is the case we use the same apns:connect/1
and apns:connect/2
functions but instead of a connection name we put undefined
:
1> apns:connect(cert, undefined).
{ok,<0.127.0>}
2> apns:connect(#{name => undefined, env => production,
certfile => "priv/cert2.pem", keyfile => "priv/key2-noenc.pem"}).
{ok,<0.130.0>}
3> apns:connect(token, my_second_connection).
{ok,<0.132.0>}
We will use apns:push_notification/3,4
.
We will need the connection, a notification, the device ID and some http2 headers. The connection is the atom
we used when we executed apns:connect/2
for setting a name or its pid
, the device ID is provided by Apple, the notification is a map
with the data we want to send, that map will be encoded to json later and the http2 headers can be explicitly sent as a parameter using apns:push_notification/4
or can be defined at the config
file, in that case we would use apns:push_notification/3
.
This is the headers
format: Header Options
-type headers() :: #{ apns_id => binary()
, apns_expiration => binary()
, apns_priority => binary()
, apns_topic => binary()
, apns_collapse_id => binary()
, apns_auth_token => binary()
}.
All of them are defined by Apple here
- apns_id can be generated by
apns_connection:new_apns_id()
Lets send a Notification.
1> {ok, Pid} = apns:connect(cert, my_first_connection).
{ok,<0.85.0>}
2> DeviceId = <<"a0dc63fb059cb9c13b03e5c974af3dd33d67fed4147da8c5ada0626439e18935">>.
<<"a0dc63fb059cb9c13b03e5c974af3dd33d67fed4147da8c5ada0626439e18935">>
3> Notification = #{aps => #{alert => <<"you have a message">>}}.
#{aps => #{alert => <<"you have a message">>}}
4> apns:push_notification(my_first_connection, DeviceId, Notification).
ok
5> apns:push_notification(Pid, DeviceId, Notification).
ok
This library works asynchronously. See Feedback Option
for feedback.
And that's all.
If network goes down or something unexpected happens the gun
connection with APNs will go down. In that case apns_erlang
will send a message {reconnecting, ServerPid}
to the client process, that means apns_erlang
lost the connection and it is trying to reconnect. Once the connection has been recover a {connection_up, ServerPid}
message will be send.
We implemented an Exponential Backoff strategy. We can set the ceiling time adding the backoff_ceiling
variable on the config
file. By default it is set to 10 (seconds).
#{
backoff_ceiling => integer()
}.
backoff_ceiling
backoff duration for reconnection in seconds. Default is 10.
Apple recommends us to keep our connections open and avoid opening and closing very often. You can check the Best Practices for Managing Connections section.
But when closing a connection makes sense apns_erlang
gives us the function apns:close_connection/1
where the parameter is the connection's name or the connection's pid
. After using it the name will be available for new connections again (if it was different than undefined
).