AnyEvent-APNS
view release on metacpan or search on metacpan
lib/AnyEvent/APNS.pm view on Meta::CPAN
package AnyEvent::APNS;
use utf8;
use Any::Moose;
use AnyEvent 4.80;
use AnyEvent::Handle;
use AnyEvent::Socket;
use AnyEvent::TLS;
require bytes;
use Carp qw(croak);
use Encode;
use Scalar::Util 'looks_like_number';
use JSON::Any;
our $VERSION = '0.10';
has certificate => (
is => 'rw',
isa => 'Str | ScalarRef',
required => 1,
);
has private_key => (
is => 'rw',
isa => 'Str | ScalarRef',
required => 1,
);
has sandbox => (
is => 'rw',
isa => 'Bool',
default => 0,
);
has handler => (
is => 'rw',
isa => 'AnyEvent::Handle',
predicate => 'connected',
clearer => 'clear_handler',
);
has json_driver => (
is => 'rw',
isa => 'Object',
lazy => 1,
default => sub {
JSON::Any->new( utf8 => 1 );
},
);
has on_error => (
is => 'rw',
isa => 'CodeRef',
default => sub { sub { warn @_ } },
);
has on_eof => (
is => 'rw',
isa => 'CodeRef',
);
has on_connect => (
is => 'rw',
isa => 'CodeRef',
default => sub { sub {} },
);
has on_error_response => (
is => 'rw',
isa => 'CodeRef',
);
has debug_port => (
is => 'rw',
isa => 'Int',
predicate => 'is_debug',
);
has _con_guard => (
is => 'rw',
isa => 'Object',
);
lib/AnyEvent/APNS.pm view on Meta::CPAN
}
$json = encode_utf8( $self->json_driver->encode($payload) );
}
$h->push_write( pack('n', bytes::length($json)) ); # payload length
$h->push_write( $json ); # payload
return $next_identifier;
}
sub _trim_utf8 {
my ($self, $string, $trim_length) = @_;
my $string_bytes = encode_utf8($string);
my $trimmed = '';
my $start_length = bytes::length($string_bytes) - $trim_length;
return $trimmed if $start_length <= 0;
for my $len ( reverse $start_length - 6 .. $start_length ) {
local $@;
eval {
$trimmed = decode_utf8(substr($string_bytes, 0, $len), Encode::FB_CROAK);
};
last if $trimmed;
}
return $trimmed;
}
sub connect {
my $self = shift;
if ($self->connected && $self->handler) {
warn 'Already connected!';
return;
}
my $host = $self->sandbox
? 'gateway.sandbox.push.apple.com'
: 'gateway.push.apple.com';
my $port = 2195;
if ($self->is_debug) {
$host = '127.0.0.1';
$port = $self->debug_port;
}
my $g = tcp_connect $host, $port, sub {
my ($fh) = @_
or return $self->on_error->(undef, 1, $!);
my $tls_setting = {};
if (ref $self->certificate) {
$tls_setting->{cert} = ${ $self->certificate };
}
else {
$tls_setting->{cert_file} = $self->certificate;
}
if (ref $self->private_key) {
$tls_setting->{key} = ${ $self->private_key };
}
else {
$tls_setting->{key_file} = $self->private_key;
}
my $handle = AnyEvent::Handle->new(
fh => $fh,
on_error => sub {
$self->on_error->(@_);
$self->clear_handler;
$_[0]->destroy;
},
!$self->is_debug ? (
tls => 'connect',
tls_ctx => $tls_setting,
) : (),
);
$self->handler( $handle );
if ($self->on_eof) {
$handle->on_eof(sub {
$self->on_eof->(@_);
$self->clear_handler;
$_[0]->destroy;
});
}
if ( $self->on_error_response ) {
$handle->on_read(
sub {
$self->_on_read_with_error_callback( @_ );
}
);
}
else {
$handle->on_read( sub { delete $_[0]->{rbuf} } );
}
$self->on_connect->();
};
Scalar::Util::weaken($self);
$self->_con_guard($g);
$self;
}
sub _on_read_with_error_callback {
my ($self, $handle) = @_;
$handle->push_read( chunk => 1,
sub {
my $command = unpack( 'C', $_[1] );
if ( $command != 8 ) {
# something is wrong
# auto reconnect
$self->clear_handler;
$self->connect;
}
});
$handle->push_read( chunk => 5,
sub {
my $status = unpack( 'C', substr( $_[1], 0, 1) );
my $identifier = unpack( 'N', substr( $_[1], 1, 4) );
$self->on_error_response->( $identifier => $status );
});
}
# 0 ... 2**32-1, 0 ... 2**32-1, 0 ...
sub _increment_identifier {
my ($self) = @_;
my $next_identifier = $self->last_identifier + 1;
if ( $next_identifier >= 2 ** 32 ) {
# identifier is only 4 bytes
$next_identifier = 0;
}
$self->last_identifier( $next_identifier );
}
__PACKAGE__->meta->make_immutable;
__END__
=for stopwords
apns SDK TODO iPhone multi-byte utf8
=head1 NAME
AnyEvent::APNS - Simple wrapper for Apple Push Notifications Service (APNS) provider
=head1 SYNOPSIS
use AnyEvent::APNS;
my $cv = AnyEvent->condvar;
my $apns; $apns = AnyEvent::APNS->new(
certificate => 'your apns certificate file',
private_key => 'your apns private key file',
sandbox => 1,
on_error => sub { # something went wrong },
on_connect => sub {
my $identifier = $apns->send( $device_token => {
aps => {
alert => 'Message received from Bob',
},
});
},
on_error_response => sub {
my ($identifier, $status) = @_;
# something wrong
},
);
$apns->connect;
# disconnect and exit program as soon as possible after sending a message
# otherwise $apns makes persistent connection with apns server
$apns->handler->on_drain(sub {
undef $_[0];
$cv->send;
});
$cv->recv;
=head1 DESCRIPTION
This module helps you to create Apple Push Notifications Service (APNS) Provider.
=head1 NOTE FOR 0.01x USERS
From 0.02, this module does not connect in constructor.
You should call connect method explicitly to connect server.
=head1 METHODS
=head2 new
Create APNS object.
my $apns = AnyEvent::APNS->new(
certificate => 'your apns certificate file',
private_key => 'your apns private key file',
sandbox => 1,
on_error => sub { # something went wrong },
);
Supported arguments are:
=over 4
=item certificate => 'Str | ScalarRef'
certificate => '/path/to/certificate_file',
# or
certificate => \$certificate,
Required. Either file path for certificate or scalar-ref of certificate data.
=item private_key => 'Str | ScalarRef'
private_key => '/path/to/private_key',
# or
private_key => \$private_key,
Required. Either file path for private_key or scalar-ref of private-key data.
=item sandbox => 0|1
This is a flag indicate target service is provisioning (sandbox => 1) or distribution (sandbox => 0)
Optional (Default: 0)
=item on_error => $cb->($handle, $fatal, $message)
Callback to be called when something error occurs.
This is wrapper for L<AnyEvent::Handle>'s on_error callbacks. Look at the document for more detail.
Optional (Default: just warn error)
=item on_eof => $cb->($handle)
Callback to be called when an end-of-file condition is detected.
Optional. (Default: use on_error instead. read L<AnyEvent::Handle> for more detail)
=item on_connect => $cb->()
Callback to be called when connection established to apns server.
Optional (Default: empty coderef)
=item on_error_response => $cb->($identifier, $status)
Callback to be called when APNs detects notification malformed or otherwise unintelligible.
C<$status> codes are documented here: L<http://developer.apple.com/library/ios/#DOCUMENTATION/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html>
C<$identifier> is the return value of C<send>.
Optional (Default: ignore)
=back
=head2 $apns->connect;
Connect to apns server.
=head2 $apns->send( $device_token, \%payload )
Send apns messages with C<\%payload> to device specified C<$device_token>.
my $identifier = $apns->send( $device_token => {
aps => {
alert => 'Message received from Bob',
},
});
C<$device_token> should be a binary 32bytes device token provided by iPhone SDK (3.0 or above)
C<\%payload> should be a hashref suitable to apple document: L<http://developer.apple.com/iPhone/library/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ApplePushService/ApplePushService.html>
Note: If you involve multi-byte strings in C<\%payload>, it should be utf8 decoded strings not utf8 bytes.
Store C<$identifier> with your C<$device_token> to react to C<on_error_response>.
( run in 0.769 second using v1.01-cache-2.11-cpan-13bb782fe5a )