File Coverage

blib/lib/Net/APNs/HTTP2.pm
Criterion Covered Total %
statement 32 94 34.0
branch 0 20 0.0
condition 0 19 0.0
subroutine 11 23 47.8
pod 3 3 100.0
total 46 159 28.9


line stmt bran cond sub pod time code
1             package Net::APNs::HTTP2;
2 1     1   539 use 5.010;
  1         3  
3 1     1   5 use strict;
  1         1  
  1         17  
4 1     1   3 use warnings;
  1         2  
  1         37  
5              
6             our $VERSION = "0.01";
7              
8 1     1   343 use Moo;
  1         8653  
  1         4  
9 1     1   1455 use Crypt::JWT;
  1         35286  
  1         44  
10 1     1   458 use JSON;
  1         5394  
  1         6  
11 1     1   345 use Cache::Memory::Simple;
  1         1984  
  1         25  
12              
13 1     1   660 use AnyEvent;
  1         3955  
  1         34  
14 1     1   457 use AnyEvent::Handle;
  1         13752  
  1         36  
15 1     1   380 use AnyEvent::Socket;
  1         10758  
  1         140  
16 1     1   475 use Protocol::HTTP2::Client;
  1         27700  
  1         982  
17              
18             has [qw/auth_key key_id team_id bundle_id is_development/] => (
19             is => 'rw',
20             );
21              
22             has apns_port => (
23             is => 'rw',
24             default => 443, # can use 2197
25             );
26              
27             has on_error => (
28             is => 'rw',
29             default => sub {
30             sub {};
31             },
32             );
33              
34             sub _host {
35 0     0     my $self = shift;
36 0 0         $self->is_development ? 'api.development.push.apple.com' : 'api.push.apple.com';
37             }
38              
39             sub _client {
40 0     0     my $self = shift;
41 0   0       $self->{_client} ||= Protocol::HTTP2::Client->new(keepalive => 1);
42             }
43              
44             sub _handle {
45 0     0     my $self = shift;
46              
47 0 0         unless ($self->_handle_connected) {
48             my $handle = AnyEvent::Handle->new(
49             keepalive => 1,
50             connect => [ $self->_host, $self->apns_port ],
51             tls => 'connect',
52             tls_ctx => {
53             verify => 1,
54             verify_peername => 'https',
55             },
56             autocork => 1,
57             on_error => sub {
58 0     0     my ($handle, $fatal, $message) = @_;
59 0           $self->on_error->($fatal, $message);
60 0           $handle->destroy;
61 0           $self->{_condvar}->send;
62             },
63             on_eof => sub {
64 0     0     my $handle = shift;
65 0           $self->{_condvar}->send;
66             },
67             on_read => sub {
68 0     0     my $handle = shift;
69 0           $self->_client->feed(delete $handle->{rbuf});
70 0           while (my $frame = $self->_client->next_frame) {
71 0           $handle->push_write($frame);
72             }
73 0 0         if ($self->_client->shutdown) {
74 0           $handle->push_shutdown;
75 0           return;
76             }
77              
78 0 0         unless ($self->_client->{active_streams} > 0) {
79 0           $self->{_condvar}->send;
80 0           return;
81             }
82             },
83 0           );
84              
85 0           $self->{_handle} = $handle;
86             }
87              
88 0           return $self->{_handle};
89             }
90              
91             sub _handle_connected {
92 0     0     my $self = shift;
93              
94 0           my $handle = $self->{_handle};
95 0 0         return if !$handle;
96 0 0         return if $handle->destroyed;
97 0           return 1;
98             }
99              
100             sub _provider_authentication_token {
101 0     0     my $self = shift;
102              
103 0   0       $self->{_cache} ||= Cache::Memory::Simple->new;
104             $self->{_cache}->get_or_set('provider_authentication_token', sub {
105 0     0     my $craims = {
106             iss => $self->team_id,
107             iat => time,
108             };
109 0           my $jwt = Crypt::JWT::encode_jwt(
110             payload => $craims,
111             key => [ $self->auth_key ],
112             alg => 'ES256',
113             extra_headers => { kid => $self->key_id },
114             );
115 0           return $jwt;
116 0           }, 60 * 50);
117             }
118              
119             sub prepare {
120 0     0 1   my ($self, $device_token, $payload, $cb, $extra_header) = @_;
121 0   0       my $apns_expiration = $extra_header->{apns_expiration} || 0;
122 0   0       my $apns_priority = $extra_header->{apns_priority} || 10;
123 0   0       my $apns_topic = $extra_header->{apns_topic} || $self->bundle_id;
124 0           my $apns_id = $extra_header->{apns_id};
125 0           my $apns_collapse_id = $extra_header->{apns_collapse_id};
126              
127 0           my $clinet = $self->_client;
128 0 0         $clinet->request(
    0          
129             ':scheme' => 'https',
130             ':authority' => join(':', $self->_host, $self->apns_port),
131             ':path' => sprintf('/3/device/%s', $device_token),
132             ':method' => 'POST',
133             headers => [
134             'authorization' => sprintf('bearer %s', $self->_provider_authentication_token),
135             'apns-expiration' => $apns_expiration,
136             'apns-priority' => $apns_priority,
137             'apns-topic' => $apns_topic,
138             defined $apns_id ? ('apns-id' => $apns_id) : (),
139             defined $apns_collapse_id ? ('apns-collapse-id' => $apns_collapse_id) : (),
140             ],
141             data => JSON::encode_json($payload),
142             on_done => $cb,
143             );
144              
145 0           return $self;
146             }
147              
148             sub send {
149 0     0 1   my $self = shift;
150              
151 0           local $self->{_condvar} = AnyEvent->condvar;
152              
153 0           my $handle = $self->_handle;
154 0           my $clinet = $self->_client;
155 0           while (my $frame = $clinet->next_frame) {
156 0           $handle->push_write($frame);
157             }
158              
159 0           $self->{_condvar}->recv;
160              
161 0           return 1;
162             }
163              
164             sub close {
165 0     0 1   my $self = shift;
166 0 0 0       if ($self->{_client} && !$self->{_client}->shutdown) {
167 0           $self->{_client}->close;
168             }
169 0 0 0       if ($self->{_handle} && !$self->{_handle}->destroyed) {
170 0           $self->{_handle}->destroy;
171             }
172 0           delete $self->{_cache};
173 0           delete $self->{_handle};
174 0           delete $self->{_client};
175              
176 0           return 1;
177             }
178              
179             1;
180             __END__