File Coverage

blib/lib/Net/APNS/Persistent.pm
Criterion Covered Total %
statement 18 60 30.0
branch 0 12 0.0
condition 0 5 0.0
subroutine 6 13 46.1
pod 3 3 100.0
total 27 93 29.0


line stmt bran cond sub pod time code
1             package Net::APNS::Persistent;
2              
3             # perl 5.8 required for utf8 safe substr
4 5     5   193572 use 5.008;
  5         20  
  5         195  
5 5     5   28 use strict;
  5         9  
  5         157  
6 5     5   26 use warnings;
  5         11  
  5         266  
7              
8             our $VERSION = '0.02';
9              
10 5     5   27 use base 'Net::APNS::Persistent::Base';
  5         9  
  5         3106  
11              
12 5     5   491728 use Encode qw(decode encode encode_utf8);
  5         235652  
  5         590  
13              
14             # NB: Using JSON::XS as not all JSON modules allowed
15             # by JSON::Any do unicode correctly
16 5     5   5900 use JSON::XS;
  5         60309  
  5         4232  
17              
18             __PACKAGE__->mk_accessors(qw(
19             devicetoken
20             _queue
21             max_payload_size
22             command
23             _json
24             ));
25              
26             my %defaults = (
27             host_production => 'gateway.push.apple.com',
28             host_sandbox => 'gateway.sandbox.push.apple.com',
29             max_payload_size => 256,
30             port => 2195,
31             command => 0
32             );
33              
34             =head1 NAME
35              
36             Net::APNS::Persistent - Send Apple APNS notifications over a persistent connection
37              
38             =head1 SYNOPSIS
39              
40             use Net::APNS::Persistent;
41              
42             my $devicetoken_hex = '04ef...a878416';
43            
44             my $apns = Net::APNS::Persistent->new({
45             sandbox => 1,
46             cert => 'cert.pem',
47             key => 'key.pem',
48             passwd => 'key password',
49             });
50              
51             $apns->queue_notification(
52             $devicetoken_hex,
53             {
54             aps => {
55             alert => 'sweet!',
56             sound => 'default',
57             badge => 1,
58             },
59             });
60            
61             $apns->send_queue;
62              
63             $apns->disconnect;
64              
65             You can queue more than one notification in one transmission
66             by calling L multiple times. If you want to
67             pass in utf8 text in the alert (either as a string or alert-Ebody),
68             you need to be careful with the encoding. See the test files for an
69             example of reading utf8 from a text file. You should also be able
70             to pass utf8 through from eg. a database in a similar way.
71              
72             You can also use the connection many times (ie. queue then send, queue then send,
73             ad nauseum). The call to disconnect is not strictly necessary since the object
74             will disconnect as soon as it falls out of scope.
75              
76             You can place your own custom data outside the C hash. L
77             Apple Push Notification Service Programming Guide for more info.
78              
79             All methods are fatal on error. Eg. if the ssl connection returns an error,
80             the code will die. You can either then just restart your script or you can use
81             C to catch the exception.
82              
83             =head1 DESCRIPTION
84              
85             Class to create a persistent connection to Apple's APNS servers
86              
87             =head1 METHODS
88              
89             =head2 new
90              
91             Args:
92              
93             =over
94              
95             =item sandbox
96              
97             set to true if you want to use the sandbox host. defaults to 0. ignored if you set the host manually
98              
99             =item cert
100              
101             path to your certificate
102              
103             =item cert_type
104              
105             defaults to PEM - see L.
106              
107             =item key
108              
109             path you your private key
110              
111             =item key_type
112              
113             defaults to PEM - see L.
114              
115             =item passwd
116              
117             password for your private key, if required.
118              
119             =item host
120              
121             defaults to gateway.push.apple.com or gateway.sandbox.push.apple.com depending
122             on the setting of sandbox. can be set manually.
123              
124             =item port
125              
126             defaults to 2195
127              
128             =item command
129              
130             defaults to 0
131              
132             =back
133              
134             NB: all these args are available as accessors, but you need to set them before the connection
135             is first used.
136              
137             =cut
138              
139             sub new {
140 0     0 1   my ($class, $init_vals) = @_;
141              
142 0   0       $init_vals ||= {};
143              
144 0           my $self = $class->SUPER::new({
145              
146             %defaults,
147            
148 0           %{$init_vals}
149             });
150              
151 0           $self->_queue([]);
152              
153 0           $self->_json(JSON::XS->new());
154 0           $self->_json->utf8(1);
155              
156 0           return $self;
157             }
158              
159             sub _apply_to_alert_body {
160 0     0     my ($payload, $func) = @_;
161              
162             return
163 0 0         if ! exists $payload->{aps}{alert};
164            
165             # can be in alert->body, or a plain string in alert
166 0 0         if (ref $payload->{aps}{alert} eq 'HASH') {
167 0           $payload->{aps}{alert}{body} = $func->($payload->{aps}{alert}{body});
168             } else {
169 0           $payload->{aps}{alert} = $func->($payload->{aps}{alert});
170             }
171             }
172              
173             sub _pack_payload_for_devicetoken {
174 0     0     my ($self, $devicetoken, $payload) = @_;
175              
176 0 0 0       if (ref($payload) ne 'HASH' || ref($payload->{aps}) ne 'HASH') {
177 0           die "Invalid payload: " . Dumper($payload);
178             }
179              
180             # force badge to be integer
181 0 0         $payload->{aps}{badge} += 0
182             if exists $payload->{aps}{badge};
183              
184             # convert message to unicode, after ensuring it was utf8 in the first place
185             _apply_to_alert_body($payload, sub {
186 0     0     my $str = shift; # decode won't work on string literals
187 0           encode('unicode', decode('utf8', $str, Encode::FB_CROAK));
188 0           });
189              
190 0           my $json = $self->_json->encode($payload);
191              
192             # enforce max_payload_size
193 0           my $max_payload_size = $self->max_payload_size;
194 0 0         if ( bytes::length($json) > $max_payload_size ) {
195            
196             # not sure why this is necessary. Must be something
197             # about the difference in density b/n utf8 and unicode?
198             # This isn't very efficient,
199             # but users shouldn't be passing in huge strings, surely...
200            
201 0           while (bytes::length($json) > $max_payload_size) {
202             _apply_to_alert_body($payload, sub {
203 0     0     substr($_[0], 0, -1);
204 0           });
205              
206 0           $json = JSON::XS::encode_json($payload);
207             }
208             }
209              
210 0           return pack(
211             'c n/a* n/a*',
212             $self->command,
213             pack( 'H*', $devicetoken ),
214             $json
215             );
216             }
217              
218             =head2 queue_notification
219              
220             takes two arguments - a device token (as a string representation of hex), and
221             a hashref with the payload. eg:
222              
223             my $devicetoken_hex = '04ef...a878416';
224              
225             $apns->queue_notification(
226             $devicetoken_hex,
227             {
228             aps => {
229             alert => 'sweet!',
230             sound => 'default',
231             badge => 1,
232             },
233             });
234              
235             $apns->queue_notification(
236             $devicetoken_hex,
237             {
238             aps => {
239             alert => {
240             body => 'foo',
241             'action-loc-key' => undef,
242             },
243             sound => 'default',
244             badge => 1,
245             },
246             foo => 'bar',
247             });
248              
249             The second example shows the complex alert format and also custom application
250             data outside the aps hash.
251              
252             This method will ensure that the payload is at most 256 bytes by trimming the
253             alert body. The trimming function is utf8-safe, but not very efficient (so
254             don't ask it to trim War and Peace).
255              
256             =cut
257              
258             sub queue_notification {
259 0     0 1   my ($self, $devicetoken, $payload) = @_;
260              
261 0           push @{$self->_queue}, [$devicetoken, $payload];
  0            
262              
263 0           return 1;
264             }
265              
266             =head2 send_queue
267              
268             This will actually send the data to the ssl connection.
269              
270             =cut
271              
272             sub send_queue {
273 0     0 1   my $self = shift;
274              
275 0           my $data = '';
276              
277 0           for my $queue_entry (@{$self->_queue}) {
  0            
278 0           my ($devicetoken, $payload) = @{$queue_entry};
  0            
279              
280 0           $data .= $self->_pack_payload_for_devicetoken($devicetoken, $payload);
281             }
282              
283 0 0         $self->_send($data)
284             if $data;
285              
286 0           $self->_queue([]);
287              
288 0           return 1;
289             }
290              
291             =head2 disconnect
292              
293             Disconnect the ssl connection and socket, and free the ssl structures. This usually
294             isn't necessary as this will happen implicitly when the object is destroyed.
295              
296             =head1 SEE ALSO
297              
298             =over 4
299              
300             =item Presentation on this module by Author
301              
302             L
303              
304             =item Apple Push Notification Service Programming Guide
305              
306             L
307              
308             =item L
309              
310             =item GIT Source Repository for this module
311              
312             L
313              
314             =back
315              
316             =head1 AUTHOR
317              
318             Mark Aufflick, Emark@aufflick.comE, L
319              
320             =head1 CREDITS
321              
322             Some inspiration came from haoyayoi's L.
323              
324             =head1 COPYRIGHT AND LICENSE
325              
326             Copyright (C) 2009 by Mark Aufflick
327              
328             This library is free software; you can redistribute it and/or modify
329             it under the same terms as Perl itself, either Perl version 5.8.9 or,
330             at your option, any later version of Perl 5 you may have available.
331              
332             =cut
333              
334             1;