File Coverage

blib/lib/Net/APNs/HTTP2.pm
Criterion Covered Total %
statement 35 98 35.7
branch 0 20 0.0
condition 0 21 0.0
subroutine 12 25 48.0
pod 3 3 100.0
total 50 167 29.9


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