File Coverage

blib/lib/WWW/Pusher/Client.pm
Criterion Covered Total %
statement 41 54 75.9
branch 3 8 37.5
condition 1 5 20.0
subroutine 13 16 81.2
pod 2 3 66.6
total 60 86 69.7


line stmt bran cond sub pod time code
1             package WWW::Pusher::Client;
2             # ABSTRACT: Laughably incomplete Perl client for Pusher WS API
3             $WWW::Pusher::Client::VERSION = '0.04';
4 1     1   111927 use strict;
  1         1  
  1         30  
5 1     1   3 use warnings;
  1         1  
  1         21  
6 1     1   15 use 5.010_001;
  1         6  
  1         35  
7 1     1   492 use Moo;
  1         10578  
  1         5  
8 1     1   1850 use JSON;
  1         9497  
  1         4  
9 1     1   118 use Carp;
  1         2  
  1         57  
10 1     1   494 use AnyEvent::WebSocket::Client;
  1         163659  
  1         50  
11 1     1   12 use Digest::SHA qw(hmac_sha256_hex);
  1         1  
  1         1246  
12              
13              
14             has 'auth_key' => (
15             is => 'ro' ,
16             required => 1
17             );
18              
19             has 'secret' => (
20             is => 'ro',
21             required => 1
22             );
23              
24             has 'channel' => (
25             is => 'rw',
26             coerce => sub {
27             my ($channel) = @_;
28              
29             if ($channel =~ /^[A-Za-z0-9_\-=@,.;]+$/) {
30             return $channel;
31             }
32             else {
33             croak 'channel is invalid';
34             }
35             },
36             predicate => 'has_channel'
37             );
38              
39             has 'client' => (
40             is => 'ro',
41             lazy => 1,
42             default => sub { AnyEvent::WebSocket::Client->new }
43             );
44              
45             has 'ws_url' => (
46             is => 'ro',
47             lazy => 1,
48             builder => sub {
49 6     6   1818 my $self = shift;
50              
51 6         112 return $self->_scheme . $self->_pusher_base . $self->_port
52             . "/app/" . $self->auth_key
53             . "?protocol=" . $self->_protocol
54             . "&client=" . $self->_client_name
55             . "&version=" . $self->_version
56             }
57             );
58              
59             has 'ws_conn' => (
60             is => 'ro',
61             lazy => 1,
62             builder => sub {
63 6     6   367 my $self = shift;
64              
65             # open the connection and immediately ->recv the condvar to
66             # return control back to us
67 6         115 return $self->client->connect($self->ws_url)->recv;
68             }
69             );
70              
71             has '_scheme' => (
72             is => 'ro',
73             default => sub { 'ws' }
74             );
75              
76             has '_port' => (
77             is => 'ro',
78             default => sub { 80 }
79             );
80              
81             has '_pusher_base' => (
82             is => 'ro',
83             default => sub { '://ws.pusherapp.com:' }
84             );
85              
86             has '_protocol' => (
87             is => 'ro',
88             default => sub { 7 }
89             );
90              
91             has '_client_name' => (
92             is => 'ro',
93             default => sub { 'perl-pusher-client' }
94             );
95              
96             has '_version' => (
97             is => 'ro',
98             default => sub { '0.001' }
99             );
100              
101             has '_socket_id' => (
102             is => 'rw',
103             coerce => sub {
104             my ($socket_id) = @_;
105              
106             if ($socket_id =~ /^\d+\.\d+$/) {
107             return $socket_id;
108             }
109             else {
110             croak 'socket_id is invalid';
111             }
112             }
113             );
114              
115              
116             sub BUILD {
117 6     6 0 127 my $self = shift;
118              
119             $self->ws_conn->on(
120             next_message => sub {
121 0     0   0 my ($conn, $message) = @_;
122 0         0 my $body = from_json($message->decoded_body);
123              
124 0 0       0 if ($body->{event} eq 'pusher:connection_established') {
125 0         0 $self->_socket_id(from_json($body->{data})->{socket_id});
126              
127 0 0       0 $self->subscribe($self->channel) if $self->has_channel;
128             }
129             else {
130 0         0 die 'Connection error?' . $message->decoded_body;
131             }
132 6         116 });
133             }
134              
135              
136             sub subscribe {
137 0     0 1 0 my ($self, $channel) = @_;
138              
139 0         0 my $data = $self->_construct_private_auth_data($channel);
140              
141 0         0 return $self->ws_conn->send(to_json({
142             event => 'pusher:subscribe',
143             data => $data
144             }));
145             }
146              
147             sub _construct_private_auth_data {
148 2     2   481 my ($self, $channel) = @_;
149 2   33     10 $channel //= $self->channel;
150              
151             # Public channels only need the channel name in the payload
152 2         7 my $data = { channel => $channel };
153              
154             # Private channels need a key:signature in the auth key for
155             # authorization
156 2 100       12 if ($channel =~ /^private-/) {
157 1         5 my $signature = $self->_socket_auth($channel);
158 1         23 $data->{auth} = $self->auth_key . ':' . $signature;
159             }
160              
161 2         5 return $data;
162             }
163              
164             sub _socket_auth {
165 2     2   230 my ($self, $channel) = @_;
166 2 50       68 die 'Missing socket_id, sorry...' unless $self->_socket_id;
167              
168 2         64 my $plainSignature = $self->_socket_id . ':' . $channel;
169 2         73 return hmac_sha256_hex($plainSignature, $self->secret);
170             }
171              
172              
173             sub trigger {
174 0     0 1   my $self = shift;
175 0   0       my $event = shift // 'ws update';
176 0           my $message = shift;
177              
178 0           $self->ws_conn->send(to_json({
179             event => $event,
180             channel => $self->channel,
181             data => $message
182             }));
183             }
184              
185              
186             1;
187              
188             __END__