Skip to content

Instantly share code, notes, and snippets.

@mash
Created June 14, 2013 02:39
Show Gist options
  • Save mash/5779104 to your computer and use it in GitHub Desktop.
Save mash/5779104 to your computer and use it in GitHub Desktop.
package AnyEvent::APNS::FeedbackService;
use utf8;
use Mouse;
use AnyEvent 4.80;
use AnyEvent::Handle;
use AnyEvent::Socket;
use AnyEvent::TLS;
our $VERSION = '0.01';
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 on_error => (
is => 'rw',
isa => 'CodeRef',
default => sub { sub { warn @_ } },
);
has on_connect => (
is => 'rw',
isa => 'CodeRef',
default => sub { sub {} },
);
has on_received_device_token => (
is => 'rw',
isa => 'CodeRef',
default => sub {
sub {
my ($data) = @_;
warn "$data->{ time } $data->{ device_token }";
};
},
);
has debug_port => (
is => 'rw',
isa => 'Int',
predicate => 'is_debug',
);
no Mouse;
sub connect {
my $self = shift;
if ($self->connected && $self->handler) {
warn 'Already connected!';
return;
}
my $host = $self->sandbox
? 'feedback.sandbox.push.apple.com'
: 'feedback.push.apple.com';
my $port = 2196;
if ($self->is_debug) {
$host = '127.0.0.1';
$port = $self->debug_port;
}
my $g; $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 {
# apple just disconnects immediately after sending their device_tokens list
# we receive a "Broken pipe" error
$self->on_error->(@_);
$self->clear_handler;
$_[0]->destroy;
undef $g;
},
on_read => sub {
$self->_on_read( @_ );
},
!$self->is_debug ? (
tls => 'connect',
tls_ctx => $tls_setting,
) : (),
);
$self->handler( $handle );
$self->on_connect->();
};
Scalar::Util::weaken($self);
$self;
}
sub _on_read {
my ($self, $handle) = @_;
$handle->push_read(
chunk => 36,
sub {
my $time = unpack( 'N', substr( $_[1], 0, 4) ); # N: big endian long
my $length = unpack( 'n', substr( $_[1], 4, 2) ); # n: big endian short
my $device_token = unpack( 'H*', substr( $_[1], 6, 32 ) ); # hex
$self->on_received_device_token->({
time => $time,
device_token => $device_token,
});
});
}
1;
__END__
=for stopwords
apns feedbackservice iPhone multi-byte utf8
=head1 NAME
AnyEvent::APNS::FeedbackService - Simple wrapper for Apple Push Notifications Service (APNS) feedback service
=head1 SYNOPSIS
use AnyEvent::APNS::FeedbackService;
my $cv = AnyEvent->condvar;
my $apns; $apns = AnyEvent::APNS::FeedbackService->new(
certificate => 'your apns certificate file',
private_key => 'your apns private key file',
sandbox => 1,
on_error => sub {
warn @_;
$cv->send; # no more device_tokens list
},
on_connect => sub {},
on_received_device_token => sub {
my ($data) = @_;
warn "$data->{ time } $data->{ device_token }";
},
);
$apns->connect;
$cv->recv;
=head1 DESCRIPTION
This module helps you to create Apple Push Notifications Service (APNS) Feedback Service
=head1 METHODS
=head2 new
Create APNS Feedback Service object.
my $apns = AnyEvent::APNS::FeedbackService->new(
certificate => 'your apns certificate file',
private_key => 'your apns private key file',
sandbox => 1,
on_error => sub {},
);
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_connect => $cb->()
Callback to be called when connection established to apns server.
Optional (Default: empty coderef)
=back
=head2 $apns->connect;
Connect to apns server.
=head2 $apns->handler
Return L<AnyEvent::Handle> object which is used to current established connection. It returns undef before connection completed.
=head1 TODO
=over 4
=item *
More correct error handling
=back
=head1 AUTHOR
Masakazu Ohtsuka <[email protected]>
=head1 COPYRIGHT AND LICENSE
This program is free software; you can redistribute
it and/or modify it under the same terms as Perl itself.
The full text of the license can be found in the
LICENSE file included with this module.
=cut
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment