Net-APNS-Simple
view release on metacpan or search on metacpan
lib/Net/APNS/Simple.pm view on Meta::CPAN
package Net::APNS::Simple;
use 5.008001;
use strict;
use warnings;
use Carp ();
use JSON;
use Moo;
use Protocol::HTTP2::Client;
use IO::Select;
use IO::Socket::SSL qw();
our $VERSION = "0.07";
has [qw/auth_key key_id team_id bundle_id development/] => (
is => 'rw',
);
has [qw/cert_file key_file passwd_cb/] => (
is => 'rw',
);
has [qw/proxy/] => (
is => 'rw',
default => $ENV{https_proxy},
);
has [qw/apns_id apns_expiration apns_collapse_id apns_push_type/] => (
is => 'rw',
);
has apns_priority => (
is => 'rw',
default => 10,
);
sub algorithm {'ES256'}
sub _host {
my ($self) = @_;
return 'api.' . ($self->development ? 'sandbox.' : '') . 'push.apple.com'
}
sub _port {443}
sub _socket {
my ($self) = @_;
if (!$self->{_socket} || !$self->{_socket}->opened){
my %ssl_opts = (
SSL_alpn_protocols => ['h2'],
);
for (qw/cert_file key_file passwd_cb/) {
$ssl_opts{"SSL_$_"} = $self->{$_} if defined $self->{$_};
}
my ($host,$port) = ($self->_host, $self->_port);
my $socket;
if ( my $proxy = $self->proxy ) {
$proxy =~ s|^http://|| or die "Invalid proxy $proxy - only http proxy is supported!\n";
require Net::HTTP;
$socket = Net::HTTP->new(PeerAddr => $proxy) || die $@;
$socket->write_request(
CONNECT => "$host:$port",
Host => "$host:$port",
Connection => "Keep-Alive",
'Proxy-Connection' => "Keep-Alive",
);
my ($code, $mess, %h) = $socket->read_response_headers;
$code eq '200' or die "Proxy error: $code $mess";
IO::Socket::SSL->start_SSL(
$socket,
# explicitly set hostname we should use for SNI
SSL_hostname => $host,
%ssl_opts,
) or die $! || $IO::Socket::SSL::SSL_ERROR;
}
else {
# TLS transport socket
$socket = IO::Socket::SSL->new(
PeerHost => $host,
PeerPort => $port,
%ssl_opts,
) or die $! || $IO::Socket::SSL::SSL_ERROR;
}
$self->{_socket} = $socket;
# non blocking
$self->{_socket}->blocking(0);
}
return $self->{_socket};
}
sub _client {
my ($self) = @_;
$self->{_client} ||= Protocol::HTTP2::Client->new(keepalive => 1);
return $self->{_client};
}
sub prepare {
my ($self, $device_token, $payload, $cb) = @_;
my @headers = (
'apns-topic' => $self->bundle_id,
);
for (qw/apns_id apns_priority apns_expiration apns_collapse_id apns_push_type/) {
my $v = $self->$_;
next unless defined $v;
my $k = $_;
$k =~ s/_/-/g;
push @headers, $k => $v;
}
if ($self->team_id and $self->auth_key and $self->key_id) {
require Crypt::PK::ECC;
# require for treat pkcs#8 private key
Crypt::PK::ECC->VERSION(0.059);
require Crypt::JWT;
my $claims = {
iss => $self->team_id,
iat => time,
};
my $jwt = Crypt::JWT::encode_jwt(
payload => $claims,
key => [$self->auth_key],
alg => $self->algorithm,
extra_headers => {
kid => $self->key_id,
},
);
push @headers, authorization => sprintf('bearer %s', $jwt);
}
my $path = sprintf '/3/device/%s', $device_token;
push @{$self->{_request}}, {
':scheme' => 'https',
':authority' => join(":", $self->_host, $self->_port),
':path' => $path,
':method' => 'POST',
headers => \@headers,
data => JSON::encode_json($payload),
on_done => $cb,
};
return $self;
}
( run in 1.381 second using v1.01-cache-2.11-cpan-39bf76dae61 )