File Coverage

blib/lib/SignalWire/Agents/Relay/Action.pm
Criterion Covered Total %
statement 76 118 64.4
branch 12 30 40.0
condition 2 21 9.5
subroutine 28 45 62.2
pod 0 17 0.0
total 118 231 51.0


line stmt bran cond sub pod time code
1             package SignalWire::Agents::Relay::Action;
2 4     4   2714 use strict;
  4         14  
  4         211  
3 4     4   43 use warnings;
  4         12  
  4         251  
4 4     4   22 use Moo;
  4         8  
  4         27  
5              
6             # Base Action class for long-running RELAY operations.
7             # Tracks control_id, completion state, and supports blocking wait.
8              
9             has 'control_id' => ( is => 'ro', required => 1 );
10             has 'call_id' => ( is => 'ro', default => sub { '' } );
11             has 'node_id' => ( is => 'ro', default => sub { '' } );
12             has 'state' => ( is => 'rw', default => sub { 'created' } );
13             has 'completed' => ( is => 'rw', default => sub { 0 } ); # boolean
14             has 'result' => ( is => 'rw', default => sub { undef } );
15             has 'events' => ( is => 'rw', default => sub { [] } );
16             has 'payload' => ( is => 'rw', default => sub { {} } ); # latest event payload
17              
18             has '_on_completed' => ( is => 'rw', default => sub { undef } );
19             has '_client' => ( is => 'rw', default => sub { undef } );
20              
21             # Register on_completed callback
22             sub on_completed {
23 3     3 0 17 my ($self, $cb) = @_;
24 3 50       6 if ($cb) {
25 3         9 $self->_on_completed($cb);
26             # If already done, fire immediately
27 3 100       5 if ($self->completed) {
28 1         2 eval { $cb->($self) };
  1         3  
29 1 50       4 warn "on_completed callback error: $@" if $@;
30             }
31 3         6 return $self;
32             }
33 0         0 return $self->_on_completed;
34             }
35              
36             # Check if the action is done
37             sub is_done {
38 4     4 0 2212 my ($self) = @_;
39 4         25 return $self->completed;
40             }
41              
42             # Blocking wait using select() polling loop
43             sub wait {
44 1     1 0 4 my ($self, %opts) = @_;
45 1   50     3 my $timeout = $opts{timeout} || 30;
46 1         2 my $start = time();
47 1   33     6 while (!$self->completed && (time() - $start) < $timeout) {
48 0         0 select(undef, undef, undef, 0.1); # sleep 100ms
49             }
50 1         4 return $self->result;
51             }
52              
53             # Called by event dispatch when an event is received for this action
54             sub _handle_event {
55 0     0   0 my ($self, $event) = @_;
56 0         0 push @{$self->events}, $event;
  0         0  
57 0   0     0 $self->payload($event->params // {});
58              
59 0 0       0 my $state = $event->can('state') ? $event->state : '';
60 0 0       0 $self->state($state) if $state;
61             }
62              
63             # Mark the action as completed with a result
64             sub _resolve {
65 7     7   547 my ($self, $result) = @_;
66 7 100       22 return if $self->completed;
67 6         15 $self->completed(1);
68 6         19 $self->result($result);
69              
70 6 100       27 if (my $cb = $self->_on_completed) {
71 2         2 eval { $cb->($self) };
  2         5  
72 2 50       9 warn "on_completed callback error: $@" if $@;
73             }
74             }
75              
76             # Send a sub-command on this action (e.g., play.stop, record.pause)
77             sub _execute_subcommand {
78 0     0   0 my ($self, $method) = @_;
79 0         0 my $client = $self->_client;
80 0 0       0 return unless $client;
81 0         0 return $client->execute($method, {
82             node_id => $self->node_id,
83             call_id => $self->call_id,
84             control_id => $self->control_id,
85             });
86             }
87              
88             # Stop the action
89             sub stop {
90 0     0 0 0 my ($self) = @_;
91 0 0       0 return if $self->completed;
92 0         0 return $self->_execute_subcommand($self->_stop_method);
93             }
94              
95             # Override in subclasses
96 0     0   0 sub _stop_method { return '' }
97              
98             # --- PlayAction ---
99             package SignalWire::Agents::Relay::Action::Play;
100 4     4   5447 use Moo;
  4         10  
  4         21  
101             extends 'SignalWire::Agents::Relay::Action';
102              
103 1     1   619 sub _stop_method { 'calling.play.stop' }
104              
105             sub pause {
106 0     0 0 0 my ($self) = @_;
107 0         0 return $self->_execute_subcommand('calling.play.pause');
108             }
109              
110             sub resume {
111 0     0 0 0 my ($self) = @_;
112 0         0 return $self->_execute_subcommand('calling.play.resume');
113             }
114              
115             sub volume {
116 0     0 0 0 my ($self, $vol) = @_;
117 0         0 my $client = $self->_client;
118 0 0       0 return unless $client;
119 0         0 return $client->execute('calling.play.volume', {
120             node_id => $self->node_id,
121             call_id => $self->call_id,
122             control_id => $self->control_id,
123             volume => $vol,
124             });
125             }
126              
127             # --- RecordAction ---
128             package SignalWire::Agents::Relay::Action::Record;
129 4     4   2726 use Moo;
  4         11  
  4         21  
130             extends 'SignalWire::Agents::Relay::Action';
131              
132 1     1   333 sub _stop_method { 'calling.record.stop' }
133              
134             sub pause {
135 0     0 0 0 my ($self, %opts) = @_;
136 0         0 my $client = $self->_client;
137 0 0       0 return unless $client;
138 0         0 my $params = {
139             node_id => $self->node_id,
140             call_id => $self->call_id,
141             control_id => $self->control_id,
142             };
143 0 0       0 $params->{behavior} = $opts{behavior} if $opts{behavior};
144 0         0 return $client->execute('calling.record.pause', $params);
145             }
146              
147             sub resume {
148 0     0 0 0 my ($self) = @_;
149 0         0 return $self->_execute_subcommand('calling.record.resume');
150             }
151              
152             # Result accessors
153 0   0 0 0 0 sub url { $_[0]->payload->{url} // '' }
154 0   0 0 0 0 sub duration { $_[0]->payload->{duration} // 0 }
155 0   0 0 0 0 sub size { $_[0]->payload->{size} // 0 }
156              
157             # --- DetectAction ---
158             package SignalWire::Agents::Relay::Action::Detect;
159 4     4   3215 use Moo;
  4         10  
  4         23  
160             extends 'SignalWire::Agents::Relay::Action';
161              
162 1     1   6 sub _stop_method { 'calling.detect.stop' }
163              
164 0   0 0 0 0 sub detect_result { $_[0]->payload->{detect} // {} }
165              
166             # --- CollectAction ---
167             package SignalWire::Agents::Relay::Action::Collect;
168 4     4   2292 use Moo;
  4         28  
  4         35  
169             extends 'SignalWire::Agents::Relay::Action';
170              
171 1     1   6 sub _stop_method { 'calling.collect.stop' }
172              
173             sub start_input_timers {
174 0     0 0 0 my ($self) = @_;
175 0         0 return $self->_execute_subcommand('calling.collect.start_input_timers');
176             }
177              
178 0   0 0 0 0 sub collect_result { $_[0]->payload->{result} // {} }
179              
180             # Override event handling: for play_and_collect, ignore play events
181             sub _handle_event {
182 1     1   6 my ($self, $event) = @_;
183             # If this is a play_and_collect action, only resolve on collect events
184 1 50       9 if ($event->event_type eq 'calling.call.play') {
185             # Ignore play events for collect actions (play_and_collect gotcha)
186 1         2 return;
187             }
188 0         0 $self->SUPER::_handle_event($event);
189             }
190              
191             # --- FaxAction ---
192             package SignalWire::Agents::Relay::Action::Fax;
193 4     4   2609 use Moo;
  4         12  
  4         18  
194             extends 'SignalWire::Agents::Relay::Action';
195              
196             has '_fax_type' => ( is => 'ro', default => sub { 'send' } );
197              
198             sub _stop_method {
199 2     2   8 my ($self) = @_;
200 2 100       12 return $self->_fax_type eq 'receive'
201             ? 'calling.receive_fax.stop'
202             : 'calling.send_fax.stop';
203             }
204              
205 0   0 0 0 0 sub fax_result { $_[0]->payload->{fax} // {} }
206              
207             # --- TapAction ---
208             package SignalWire::Agents::Relay::Action::Tap;
209 4     4   2424 use Moo;
  4         8  
  4         19  
210             extends 'SignalWire::Agents::Relay::Action';
211              
212 1     1   6 sub _stop_method { 'calling.tap.stop' }
213              
214             # --- StreamAction ---
215             package SignalWire::Agents::Relay::Action::Stream;
216 4     4   1574 use Moo;
  4         8  
  4         14  
217             extends 'SignalWire::Agents::Relay::Action';
218              
219 1     1   6 sub _stop_method { 'calling.stream.stop' }
220              
221             # --- PayAction ---
222             package SignalWire::Agents::Relay::Action::Pay;
223 4     4   1651 use Moo;
  4         23  
  4         36  
224             extends 'SignalWire::Agents::Relay::Action';
225              
226 1     1   6 sub _stop_method { 'calling.pay.stop' }
227              
228 0   0 0 0 0 sub pay_result { $_[0]->payload->{result} // {} }
229              
230             # --- TranscribeAction ---
231             package SignalWire::Agents::Relay::Action::Transcribe;
232 4     4   1849 use Moo;
  4         15  
  4         17  
233             extends 'SignalWire::Agents::Relay::Action';
234              
235 1     1   7 sub _stop_method { 'calling.transcribe.stop' }
236              
237             # --- AIAction ---
238             package SignalWire::Agents::Relay::Action::AI;
239 4     4   1779 use Moo;
  4         2173  
  4         22  
240             extends 'SignalWire::Agents::Relay::Action';
241              
242 1     1   8 sub _stop_method { 'calling.ai.stop' }
243              
244             1;