line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package AnyEvent::APNS; |
2
|
5
|
|
|
5
|
|
49825
|
use utf8; |
|
5
|
|
|
|
|
38
|
|
|
5
|
|
|
|
|
29
|
|
3
|
5
|
|
|
5
|
|
5192
|
use Any::Moose; |
|
5
|
|
|
|
|
199947
|
|
|
5
|
|
|
|
|
37
|
|
4
|
|
|
|
|
|
|
|
5
|
5
|
|
|
5
|
|
10670
|
use AnyEvent 4.80; |
|
5
|
|
|
|
|
26637
|
|
|
5
|
|
|
|
|
173
|
|
6
|
5
|
|
|
5
|
|
5086
|
use AnyEvent::Handle; |
|
5
|
|
|
|
|
67693
|
|
|
5
|
|
|
|
|
184
|
|
7
|
5
|
|
|
5
|
|
5072
|
use AnyEvent::Socket; |
|
5
|
|
|
|
|
40530
|
|
|
5
|
|
|
|
|
744
|
|
8
|
5
|
|
|
5
|
|
5796
|
use AnyEvent::TLS; |
|
5
|
|
|
|
|
103776
|
|
|
5
|
|
|
|
|
292
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
require bytes; |
11
|
5
|
|
|
5
|
|
73
|
use Carp qw(croak); |
|
5
|
|
|
|
|
10
|
|
|
5
|
|
|
|
|
401
|
|
12
|
5
|
|
|
5
|
|
6061
|
use Encode; |
|
5
|
|
|
|
|
64996
|
|
|
5
|
|
|
|
|
547
|
|
13
|
5
|
|
|
5
|
|
73
|
use Scalar::Util 'looks_like_number'; |
|
5
|
|
|
|
|
12
|
|
|
5
|
|
|
|
|
471
|
|
14
|
5
|
|
|
5
|
|
6733
|
use JSON::Any; |
|
5
|
|
|
|
|
99886
|
|
|
5
|
|
|
|
|
39
|
|
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
our $VERSION = '0.10'; |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
has certificate => ( |
19
|
|
|
|
|
|
|
is => 'rw', |
20
|
|
|
|
|
|
|
isa => 'Str | ScalarRef', |
21
|
|
|
|
|
|
|
required => 1, |
22
|
|
|
|
|
|
|
); |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
has private_key => ( |
25
|
|
|
|
|
|
|
is => 'rw', |
26
|
|
|
|
|
|
|
isa => 'Str | ScalarRef', |
27
|
|
|
|
|
|
|
required => 1, |
28
|
|
|
|
|
|
|
); |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
has sandbox => ( |
31
|
|
|
|
|
|
|
is => 'rw', |
32
|
|
|
|
|
|
|
isa => 'Bool', |
33
|
|
|
|
|
|
|
default => 0, |
34
|
|
|
|
|
|
|
); |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
has handler => ( |
37
|
|
|
|
|
|
|
is => 'rw', |
38
|
|
|
|
|
|
|
isa => 'AnyEvent::Handle', |
39
|
|
|
|
|
|
|
predicate => 'connected', |
40
|
|
|
|
|
|
|
clearer => 'clear_handler', |
41
|
|
|
|
|
|
|
); |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
has json_driver => ( |
44
|
|
|
|
|
|
|
is => 'rw', |
45
|
|
|
|
|
|
|
isa => 'Object', |
46
|
|
|
|
|
|
|
lazy => 1, |
47
|
|
|
|
|
|
|
default => sub { |
48
|
|
|
|
|
|
|
JSON::Any->new( utf8 => 1 ); |
49
|
|
|
|
|
|
|
}, |
50
|
|
|
|
|
|
|
); |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
has on_error => ( |
53
|
|
|
|
|
|
|
is => 'rw', |
54
|
|
|
|
|
|
|
isa => 'CodeRef', |
55
|
|
|
|
|
|
|
default => sub { sub { warn @_ } }, |
56
|
|
|
|
|
|
|
); |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
has on_eof => ( |
59
|
|
|
|
|
|
|
is => 'rw', |
60
|
|
|
|
|
|
|
isa => 'CodeRef', |
61
|
|
|
|
|
|
|
); |
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
has on_connect => ( |
64
|
|
|
|
|
|
|
is => 'rw', |
65
|
|
|
|
|
|
|
isa => 'CodeRef', |
66
|
|
|
|
|
|
|
default => sub { sub {} }, |
67
|
|
|
|
|
|
|
); |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
has on_error_response => ( |
70
|
|
|
|
|
|
|
is => 'rw', |
71
|
|
|
|
|
|
|
isa => 'CodeRef', |
72
|
|
|
|
|
|
|
); |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
has debug_port => ( |
75
|
|
|
|
|
|
|
is => 'rw', |
76
|
|
|
|
|
|
|
isa => 'Int', |
77
|
|
|
|
|
|
|
predicate => 'is_debug', |
78
|
|
|
|
|
|
|
); |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
has _con_guard => ( |
81
|
|
|
|
|
|
|
is => 'rw', |
82
|
|
|
|
|
|
|
isa => 'Object', |
83
|
|
|
|
|
|
|
); |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
has last_identifier => ( |
86
|
|
|
|
|
|
|
is => 'rw', |
87
|
|
|
|
|
|
|
isa => 'Int', |
88
|
|
|
|
|
|
|
default => sub { 0; } |
89
|
|
|
|
|
|
|
); |
90
|
|
|
|
|
|
|
|
91
|
5
|
|
|
5
|
|
39292
|
no Any::Moose; |
|
5
|
|
|
|
|
13
|
|
|
5
|
|
|
|
|
50
|
|
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
sub send { |
94
|
3
|
|
|
3
|
1
|
26
|
my $self = shift; |
95
|
3
|
|
|
|
|
8
|
my ($token, $payload, $expiry) = @_; |
96
|
|
|
|
|
|
|
|
97
|
3
|
|
|
|
|
26
|
my $json = encode_utf8( $self->json_driver->encode($payload) ); |
98
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
# http://developer.apple.com/library/ios/#DOCUMENTATION/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html |
100
|
|
|
|
|
|
|
# Expiry—A fixed UNIX epoch date expressed in seconds (UTC) that identifies when the notification is no longer valid and can be discarded. The expiry value should be in network order (big endian). If the expiry value is positive, APNs tries to deliver the notification at least once. You can specify zero or a value less than zero to request that APNs not store the notification at all. |
101
|
|
|
|
|
|
|
# default to 24 hours |
102
|
3
|
50
|
|
|
|
665
|
$expiry = defined $expiry ? $expiry : time() + 3600 * 24; |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
# Identifier—An arbitrary value that identifies this notification. This same identifier is returned in a error-response packet if APNs cannot interpret a notification. |
105
|
3
|
|
|
|
|
14
|
my $next_identifier = $self->_increment_identifier; |
106
|
|
|
|
|
|
|
|
107
|
3
|
|
|
|
|
11
|
my $h = $self->handler; |
108
|
|
|
|
|
|
|
|
109
|
3
|
|
|
|
|
19
|
$h->push_write( pack('C', 1) ); # command |
110
|
3
|
|
|
|
|
284
|
$h->push_write( pack('N', $next_identifier) ); |
111
|
3
|
|
|
|
|
175
|
$h->push_write( pack('N', $expiry) ); |
112
|
3
|
|
|
|
|
177
|
$h->push_write( pack('n', bytes::length($token)) ); # token length |
113
|
3
|
|
|
|
|
2453
|
$h->push_write( $token ); # device token |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
# Apple Push Notification Service refuses string values as badge number |
116
|
3
|
50
|
33
|
|
|
176
|
if ($payload->{aps}{badge} && looks_like_number($payload->{aps}{badge})) { |
117
|
0
|
|
|
|
|
0
|
$payload->{aps}{badge} += 0; |
118
|
|
|
|
|
|
|
} |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
# The maximum size allowed for a notification payload is 256 bytes; |
121
|
|
|
|
|
|
|
# Apple Push Notification Service refuses any notification that exceeds this limit. |
122
|
3
|
100
|
|
|
|
14
|
if ( (my $exceeded = bytes::length($json) - 256) > 0 ) { |
123
|
2
|
100
|
|
|
|
20
|
if (ref $payload->{aps}{alert} eq 'HASH') { |
124
|
1
|
|
|
|
|
182
|
$payload->{aps}{alert}{body} = |
125
|
|
|
|
|
|
|
$self->_trim_utf8($payload->{aps}{alert}{body}, $exceeded); |
126
|
|
|
|
|
|
|
} |
127
|
|
|
|
|
|
|
else { |
128
|
1
|
|
|
|
|
6
|
$payload->{aps}{alert} = $self->_trim_utf8($payload->{aps}{alert}, $exceeded); |
129
|
|
|
|
|
|
|
} |
130
|
|
|
|
|
|
|
|
131
|
2
|
|
|
|
|
16
|
$json = encode_utf8( $self->json_driver->encode($payload) ); |
132
|
|
|
|
|
|
|
} |
133
|
|
|
|
|
|
|
|
134
|
3
|
|
|
|
|
87
|
$h->push_write( pack('n', bytes::length($json)) ); # payload length |
135
|
3
|
|
|
|
|
207
|
$h->push_write( $json ); # payload |
136
|
|
|
|
|
|
|
|
137
|
3
|
|
|
|
|
191
|
return $next_identifier; |
138
|
|
|
|
|
|
|
} |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
sub _trim_utf8 { |
141
|
2
|
|
|
2
|
|
6
|
my ($self, $string, $trim_length) = @_; |
142
|
|
|
|
|
|
|
|
143
|
2
|
|
|
|
|
8
|
my $string_bytes = encode_utf8($string); |
144
|
2
|
|
|
|
|
13
|
my $trimmed = ''; |
145
|
|
|
|
|
|
|
|
146
|
2
|
|
|
|
|
6
|
my $start_length = bytes::length($string_bytes) - $trim_length; |
147
|
2
|
50
|
|
|
|
12
|
return $trimmed if $start_length <= 0; |
148
|
|
|
|
|
|
|
|
149
|
2
|
|
|
|
|
8
|
for my $len ( reverse $start_length - 6 .. $start_length ) { |
150
|
6
|
|
|
|
|
7
|
local $@; |
151
|
6
|
|
|
|
|
8
|
eval { |
152
|
6
|
|
|
|
|
34
|
$trimmed = decode_utf8(substr($string_bytes, 0, $len), Encode::FB_CROAK); |
153
|
|
|
|
|
|
|
}; |
154
|
6
|
100
|
|
|
|
79
|
last if $trimmed; |
155
|
|
|
|
|
|
|
} |
156
|
|
|
|
|
|
|
|
157
|
2
|
|
|
|
|
10
|
return $trimmed; |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
sub connect { |
161
|
5
|
|
|
5
|
1
|
12547
|
my $self = shift; |
162
|
|
|
|
|
|
|
|
163
|
5
|
50
|
33
|
|
|
40
|
if ($self->connected && $self->handler) { |
164
|
0
|
|
|
|
|
0
|
warn 'Already connected!'; |
165
|
0
|
|
|
|
|
0
|
return; |
166
|
|
|
|
|
|
|
} |
167
|
|
|
|
|
|
|
|
168
|
5
|
50
|
|
|
|
31
|
my $host = $self->sandbox |
169
|
|
|
|
|
|
|
? 'gateway.sandbox.push.apple.com' |
170
|
|
|
|
|
|
|
: 'gateway.push.apple.com'; |
171
|
5
|
|
|
|
|
10
|
my $port = 2195; |
172
|
|
|
|
|
|
|
|
173
|
5
|
50
|
|
|
|
26
|
if ($self->is_debug) { |
174
|
5
|
|
|
|
|
12
|
$host = '127.0.0.1'; |
175
|
5
|
|
|
|
|
18
|
$port = $self->debug_port; |
176
|
|
|
|
|
|
|
} |
177
|
|
|
|
|
|
|
my $g = tcp_connect $host, $port, sub { |
178
|
5
|
50
|
|
5
|
|
478
|
my ($fh) = @_ |
179
|
|
|
|
|
|
|
or return $self->on_error->(undef, 1, $!); |
180
|
|
|
|
|
|
|
|
181
|
5
|
|
|
|
|
11
|
my $tls_setting = {}; |
182
|
5
|
100
|
|
|
|
32
|
if (ref $self->certificate) { |
183
|
2
|
|
|
|
|
3
|
$tls_setting->{cert} = ${ $self->certificate }; |
|
2
|
|
|
|
|
9
|
|
184
|
|
|
|
|
|
|
} |
185
|
|
|
|
|
|
|
else { |
186
|
3
|
|
|
|
|
17
|
$tls_setting->{cert_file} = $self->certificate; |
187
|
|
|
|
|
|
|
} |
188
|
|
|
|
|
|
|
|
189
|
5
|
100
|
|
|
|
28
|
if (ref $self->private_key) { |
190
|
2
|
|
|
|
|
3
|
$tls_setting->{key} = ${ $self->private_key }; |
|
2
|
|
|
|
|
8
|
|
191
|
|
|
|
|
|
|
} |
192
|
|
|
|
|
|
|
else { |
193
|
3
|
|
|
|
|
15
|
$tls_setting->{key_file} = $self->private_key; |
194
|
|
|
|
|
|
|
} |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
my $handle = AnyEvent::Handle->new( |
197
|
|
|
|
|
|
|
fh => $fh, |
198
|
|
|
|
|
|
|
on_error => sub { |
199
|
1
|
|
|
|
|
180
|
$self->on_error->(@_); |
200
|
1
|
|
|
|
|
15
|
$self->clear_handler; |
201
|
1
|
|
|
|
|
6
|
$_[0]->destroy; |
202
|
|
|
|
|
|
|
}, |
203
|
5
|
50
|
|
|
|
77
|
!$self->is_debug ? ( |
204
|
|
|
|
|
|
|
tls => 'connect', |
205
|
|
|
|
|
|
|
tls_ctx => $tls_setting, |
206
|
|
|
|
|
|
|
) : (), |
207
|
|
|
|
|
|
|
); |
208
|
5
|
|
|
|
|
481
|
$self->handler( $handle ); |
209
|
|
|
|
|
|
|
|
210
|
5
|
100
|
|
|
|
26
|
if ($self->on_eof) { |
211
|
|
|
|
|
|
|
$handle->on_eof(sub { |
212
|
1
|
|
|
|
|
140
|
$self->on_eof->(@_); |
213
|
1
|
|
|
|
|
28
|
$self->clear_handler; |
214
|
1
|
|
|
|
|
5
|
$_[0]->destroy; |
215
|
1
|
|
|
|
|
7
|
}); |
216
|
|
|
|
|
|
|
} |
217
|
|
|
|
|
|
|
|
218
|
5
|
50
|
|
|
|
27
|
if ( $self->on_error_response ) { |
219
|
|
|
|
|
|
|
$handle->on_read( |
220
|
|
|
|
|
|
|
sub { |
221
|
0
|
|
|
|
|
0
|
$self->_on_read_with_error_callback( @_ ); |
222
|
|
|
|
|
|
|
} |
223
|
0
|
|
|
|
|
0
|
); |
224
|
|
|
|
|
|
|
} |
225
|
|
|
|
|
|
|
else { |
226
|
5
|
|
|
|
|
35
|
$handle->on_read( sub { delete $_[0]->{rbuf} } ); |
|
0
|
|
|
|
|
0
|
|
227
|
|
|
|
|
|
|
} |
228
|
|
|
|
|
|
|
|
229
|
5
|
|
|
|
|
234
|
$self->on_connect->(); |
230
|
5
|
|
|
|
|
53
|
}; |
231
|
|
|
|
|
|
|
|
232
|
5
|
|
|
|
|
2322
|
Scalar::Util::weaken($self); |
233
|
5
|
|
|
|
|
52
|
$self->_con_guard($g); |
234
|
|
|
|
|
|
|
|
235
|
5
|
|
|
|
|
14
|
$self; |
236
|
|
|
|
|
|
|
} |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
sub _on_read_with_error_callback { |
239
|
0
|
|
|
0
|
|
0
|
my ($self, $handle) = @_; |
240
|
|
|
|
|
|
|
$handle->push_read( chunk => 1, |
241
|
|
|
|
|
|
|
sub { |
242
|
0
|
|
|
0
|
|
0
|
my $command = unpack( 'C', $_[1] ); |
243
|
0
|
0
|
|
|
|
0
|
if ( $command != 8 ) { |
244
|
|
|
|
|
|
|
# something is wrong |
245
|
|
|
|
|
|
|
# auto reconnect |
246
|
0
|
|
|
|
|
0
|
$self->clear_handler; |
247
|
0
|
|
|
|
|
0
|
$self->connect; |
248
|
|
|
|
|
|
|
} |
249
|
0
|
|
|
|
|
0
|
}); |
250
|
|
|
|
|
|
|
$handle->push_read( chunk => 5, |
251
|
|
|
|
|
|
|
sub { |
252
|
0
|
|
|
0
|
|
0
|
my $status = unpack( 'C', substr( $_[1], 0, 1) ); |
253
|
0
|
|
|
|
|
0
|
my $identifier = unpack( 'N', substr( $_[1], 1, 4) ); |
254
|
0
|
|
|
|
|
0
|
$self->on_error_response->( $identifier => $status ); |
255
|
0
|
|
|
|
|
0
|
}); |
256
|
|
|
|
|
|
|
} |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
# 0 ... 2**32-1, 0 ... 2**32-1, 0 ... |
259
|
|
|
|
|
|
|
sub _increment_identifier { |
260
|
3
|
|
|
3
|
|
8
|
my ($self) = @_; |
261
|
3
|
|
|
|
|
13
|
my $next_identifier = $self->last_identifier + 1; |
262
|
3
|
50
|
|
|
|
13
|
if ( $next_identifier >= 2 ** 32 ) { |
263
|
|
|
|
|
|
|
# identifier is only 4 bytes |
264
|
0
|
|
|
|
|
0
|
$next_identifier = 0; |
265
|
|
|
|
|
|
|
} |
266
|
3
|
|
|
|
|
15
|
$self->last_identifier( $next_identifier ); |
267
|
|
|
|
|
|
|
} |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
__PACKAGE__->meta->make_immutable; |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
__END__ |