Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ jobs:
runs-on: ${{matrix.os}}
strategy:
matrix:
otp_vsn: [24, 25, 26, 27]
otp_vsn: [27, 28, 29]
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v2
- uses: erlef/setup-beam@v1.18.1
- uses: actions/checkout@v4
- uses: erlef/setup-beam@v1.24.1
with:
otp-version: ${{matrix.otp_vsn}}
rebar3-version: '3.22.1'
rebar3-version: '3.26.0'
- run: rebar3 as test xref
- run: rebar3 as test dialyzer
- run: rebar3 hank
- run: rebar3 lint
# - run: rebar3 as test ct
- run: rebar3 as test ct
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ If you find any **bugs** or have a **problem** while using Apns4erl, please [ope

## Requirements
- 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 R19+
- Erlang/OTP 27+ (JSON encoding uses the `json` module from stdlib, introduced in OTP 27)

## Important Links

Expand Down Expand Up @@ -180,10 +180,10 @@ Lets send a Notification.
The result is the response itself, its format is:

```erlang
-type response() :: { integer() % HTTP2 Code
, [term()] % Response Headers
, [term()] | no_body % Response Body
} | timeout.
-type response() :: { integer() % HTTP2 Code
, [term()] % Response Headers
, binary() | no_body % Response Body
} | {error, term()}.
```

And that's all.
Expand Down Expand Up @@ -233,8 +233,11 @@ We can use this token for an entire hour, after that we will receive something l

## Pushing notifications

*NOTE* in order to push notifications, in both ways, we _must_ call `apns:push_notification/3,4` and `apns:push_notification_token/4,5` from the same
process which created the connection. If we try to do it from a different one we will get an error `{error, not_connection_owner}`.
`apns:push_notification/3,4` and `apns:push_notification_token/4,5` can be called from any process, not only
the one that created the connection. Each request gets its own HTTP2 stream and is replied to individually.

Note that the process which created the connection is still the one that receives the `{reconnecting, ServerPid}`
and `{connection_up, ServerPid}` messages described in the [Reconnection](#reconnection) section.

## Reconnection

Expand Down
52 changes: 21 additions & 31 deletions elvis.config
Original file line number Diff line number Diff line change
@@ -1,34 +1,24 @@
%% elvis_core 6 dropped the {elvis, [{config, ...}]} wrapper, replaced
%% dirs+filter with `files' globs, removed the elvis_config ruleset, and
%% renamed line_length to max_line_length and god_modules to no_god_modules.
[
{
elvis,
[
{config,
[#{dirs => ["src", "test"],
filter => "*.erl",
ruleset => erl_files,
rules => [
{elvis_text_style, line_length, #{limit => 100}}
, {elvis_style, god_modules, #{limit => 25, ignore => [apns_connection]}}
, {elvis_style, no_author, disable}
, {elvis_style, no_space, disable}
, {elvis_style, no_if_expression, #{ignore => [apns_connection]}}
, {elvis_style, dont_repeat_yourself, #{min_complexity => 20}}
, {elvis_style, export_used_types, #{ignore => [apns_meta_SUITE,
apns_os_SUITE,
connection_SUITE,
feedback_SUITE]}}
]
},
#{dirs => ["."],
filter => "rebar.config",
ruleset => rebar_config
},
#{dirs => ["."],
filter => "elvis.config",
ruleset => elvis_config
}
]
{config,
[#{files => ["src/**/*.erl", "test/**/*.erl"],
ruleset => erl_files,
rules => [
{elvis_text_style, max_line_length, #{limit => 100}}
, {elvis_style, no_god_modules, #{limit => 25, ignore => [apns_connection]}}
, {elvis_style, no_author, disable}
, {elvis_style, no_space, disable}
, {elvis_style, no_if_expression, #{ignore => [apns_connection]}}
, {elvis_style, dont_repeat_yourself, #{min_complexity => 20}}
, {elvis_style, export_used_types, #{ignore => [apns_os_SUITE,
connection_SUITE,
feedback_SUITE]}}
]
},
#{files => ["rebar.config"],
ruleset => rebar_config
}
]
}
]}
].
26 changes: 14 additions & 12 deletions rebar.config
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,27 @@
debug_info
]}.

{minimum_otp_vsn, "24"}.
%% OTP 27 is the first release shipping the built-in `json` module, which
%% replaced our jsx dependency.
{minimum_otp_vsn, "27"}.

%% == Dependencies ==

{deps, [
{gun, "2.1.0"},
{jsx, "3.1.0"},
{base64url, "1.0.1"}
]}.

%% == Plugins ==
%% Don't go below these. Both plugins are built with warnings_as_errors, so
%% older releases fail on newer compilers: earlier ones pull a katana_code
%% without `nowarn_deprecated_catch' (breaks from OTP 28 on), and elvis_core
%% below 5 exports a binding from a subexpression (breaks on OTP 29). They
%% also share one plugin directory, so both must agree on a katana_code 2.5.x.
{project_plugins, [
{rebar3_hank, "1.4.1"},
{rebar3_lint, "3.2.6"},
{rebar3_depup, "0.4.0"}
{rebar3_hank, "2.0.0"},
{rebar3_lint, "6.0.0"},
{rebar3_depup, "1.0.0"}
]}.

%% == Profiles ==
Expand All @@ -43,16 +49,12 @@
nowarn_missing_spec
]},
{deps, [
{katana_test, "1.0.1"},
{katana, "1.0.0"},
{mixer, "1.1.1", {pkg, inaka_mixer}},
{meck, "0.9.2"}
{meck, "1.2.0"}
]},
{dialyzer, [
{plt_extra_apps, [
meck,
katana,
katana_test
common_test,
meck
]}
]}
]}
Expand Down
9 changes: 3 additions & 6 deletions rebar.lock
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
{"1.2.0",
[{<<"base64url">>,{pkg,<<"base64url">>,<<"1.0.1">>},0},
{<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.13.0">>},1},
{<<"gun">>,{pkg,<<"gun">>,<<"2.1.0">>},0},
{<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},0}]}.
{<<"gun">>,{pkg,<<"gun">>,<<"2.1.0">>},0}]}.
[
{pkg_hash,[
{<<"base64url">>, <<"F8C7F2DA04CA9A5D0F5F50258F055E1D699F0E8BF4CFDB30B750865368403CF6">>},
{<<"cowlib">>, <<"DB8F7505D8332D98EF50A3EF34B34C1AFDDEC7506E4EE4DD4A3A266285D282CA">>},
{<<"gun">>, <<"B4E4CBBF3026D21981C447E9E7CA856766046EFF693720BA43114D7F5DE36E87">>},
{<<"jsx">>, <<"D12516BAA0BB23A59BB35DCCAF02A1BD08243FCBB9EFE24F2D9D056CCFF71268">>}]},
{<<"gun">>, <<"B4E4CBBF3026D21981C447E9E7CA856766046EFF693720BA43114D7F5DE36E87">>}]},
{pkg_hash_ext,[
{<<"base64url">>, <<"F9B3ADD4731A02A9B0410398B475B33E7566A695365237A6BDEE1BB447719F5C">>},
{<<"cowlib">>, <<"E1E1284DC3FC030A64B1AD0D8382AE7E99DA46C3246B815318A4B848873800A4">>},
{<<"gun">>, <<"52FC7FC246BFC3B00E01AEA1C2854C70A366348574AB50C57DFE796D24A0101D">>},
{<<"jsx">>, <<"0C5CC8FDC11B53CC25CF65AC6705AD39E54ECC56D1C22E4ADB8F5A53FB9427F3">>}]}
{<<"gun">>, <<"52FC7FC246BFC3B00E01AEA1C2854C70A366348574AB50C57DFE796D24A0101D">>}]}
].
1 change: 0 additions & 1 deletion src/apns.app.src
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
kernel,
stdlib,
ssl,
jsx,
gun,
base64url
]},
Expand Down
44 changes: 23 additions & 21 deletions src/apns.erl
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,20 @@
]).

-export_type([ json/0
, json_value/0
, device_id/0
, response/0
, token/0
, headers/0
]).

-type json() :: #{binary() => binary() | json()}.
-type device_id() :: binary().
-type response() :: { integer() % HTTP2 Code
, [term()] % Response Headers
, [term()] | no_body % Response Body
} | timeout.
-type json() :: #{atom() | binary() => json_value()}.
-type json_value() :: atom() | number() | binary() | [json_value()] | json().
-type device_id() :: binary().
-type response() :: { integer() % HTTP2 Code
, [term()] % Response Headers
, binary() | no_body % Response Body
} | {error, term()}.
-type token() :: binary().
-type headers() :: #{ apns_id => binary()
, apns_expiration => binary()
Expand Down Expand Up @@ -103,7 +105,7 @@ close_connection(ConnectionId) ->
-spec push_notification( apns_connection:name() | pid()
, device_id()
, json()
) -> response() | {error, not_connection_owner}.
) -> response().
push_notification(ConnectionId, DeviceId, JSONMap) ->
Headers = default_headers(),
push_notification(ConnectionId, DeviceId, JSONMap, Headers).
Expand All @@ -113,9 +115,9 @@ push_notification(ConnectionId, DeviceId, JSONMap) ->
, device_id()
, json()
, headers()
) -> response() | {error, not_connection_owner}.
) -> response().
push_notification(ConnectionId, DeviceId, JSONMap, Headers) ->
Notification = jsx:encode(JSONMap),
Notification = iolist_to_binary(json:encode(JSONMap)),
apns_connection:push_notification( ConnectionId
, DeviceId
, Notification
Expand All @@ -128,7 +130,7 @@ push_notification(ConnectionId, DeviceId, JSONMap, Headers) ->
, token()
, device_id()
, json()
) -> response() | {error, not_connection_owner}.
) -> response().
push_notification_token(ConnectionId, Token, DeviceId, JSONMap) ->
Headers = default_headers(),
push_notification_token(ConnectionId, Token, DeviceId, JSONMap, Headers).
Expand All @@ -139,9 +141,9 @@ push_notification_token(ConnectionId, Token, DeviceId, JSONMap) ->
, device_id()
, json()
, headers()
) -> response() | {error, not_connection_owner}.
) -> response().
push_notification_token(ConnectionId, Token, DeviceId, JSONMap, Headers) ->
Notification = jsx:encode(JSONMap),
Notification = iolist_to_binary(json:encode(JSONMap)),
apns_connection:push_notification( ConnectionId
, Token
, DeviceId
Expand All @@ -156,15 +158,15 @@ generate_token(TeamId, KeyId) ->
-spec generate_token(binary(), binary(), string()) -> token().
generate_token(TeamId, KeyId, KeyPath) ->
Algorithm = <<"ES256">>,
Header = jsx:encode([ {alg, Algorithm}
, {typ, <<"JWT">>}
, {kid, KeyId}
]),
Payload = jsx:encode([ {iss, TeamId}
, {iat, apns_utils:epoch()}
]),
HeaderEncoded = base64url:encode(Header),
PayloadEncoded = base64url:encode(Payload),
Header = json:encode(#{ alg => Algorithm
, typ => <<"JWT">>
, kid => KeyId
}),
Payload = json:encode(#{ iss => TeamId
, iat => apns_utils:epoch()
}),
HeaderEncoded = base64url:encode(iolist_to_binary(Header)),
PayloadEncoded = base64url:encode(iolist_to_binary(Payload)),
DataEncoded = <<HeaderEncoded/binary, $., PayloadEncoded/binary>>,
Signature = apns_utils:sign(DataEncoded, KeyPath),
<<DataEncoded/binary, $., Signature/binary>>.
Expand Down
4 changes: 2 additions & 2 deletions src/apns_connection.erl
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ gun_pid(ConnectionId) ->
-spec push_notification( name() | pid()
, apns:device_id()
, notification()
, apns:headers()) -> apns:response() | {error, not_connection_owner}.
, apns:headers()) -> apns:response().
push_notification(ConnectionId, DeviceId, Notification, Headers) ->
gen_statem:call(ConnectionId, {push_notification, DeviceId, Notification, Headers}).

Expand All @@ -194,7 +194,7 @@ push_notification(ConnectionId, DeviceId, Notification, Headers) ->
, apns:token()
, apns:device_id()
, notification()
, apns:headers()) -> apns:response() | {error, not_connection_owner}.
, apns:headers()) -> apns:response().
push_notification(ConnectionId, Token, DeviceId, Notification, Headers) ->
gen_statem:call(ConnectionId, {push_notification, Token, DeviceId, Notification, Headers}).

Expand Down
26 changes: 0 additions & 26 deletions test/apns_meta_SUITE.erl

This file was deleted.

Loading
Loading