Class: Switest::Call

Inherits:
Object
  • Object
show all
Defined in:
lib/switest/call.rb

Constant Summary collapse

CALLSTATE_MAP =
{ "RINGING" => :ringing, "ACTIVE" => :answered }.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(id:, direction:, to: nil, from: nil, headers: {}, session: nil) ⇒ Call

Returns a new instance of Call.



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/switest/call.rb', line 15

def initialize(id:, direction:, to: nil, from: nil, headers: {}, session: nil)
  @id = id
  @session = session
  @direction = direction  # :inbound or :outbound
  @to = to
  @from = from
  @headers = headers.is_a?(Hash) ? headers.dup : {}

  @state = :new
  @start_time = Time.now
  @answer_time = nil
  @end_time = nil
  @end_reason = nil

  @dtmf_queue = Async::Queue.new

  @answered_promise = Async::Promise.new
  @ended_promise = Async::Promise.new
end

Instance Attribute Details

#answer_timeObject (readonly)

Returns the value of attribute answer_time.



12
13
14
# File 'lib/switest/call.rb', line 12

def answer_time
  @answer_time
end

#directionObject (readonly)

Returns the value of attribute direction.



11
12
13
# File 'lib/switest/call.rb', line 11

def direction
  @direction
end

#end_reasonObject (readonly)

Returns the value of attribute end_reason.



12
13
14
# File 'lib/switest/call.rb', line 12

def end_reason
  @end_reason
end

#end_timeObject (readonly)

Returns the value of attribute end_time.



12
13
14
# File 'lib/switest/call.rb', line 12

def end_time
  @end_time
end

#fromObject (readonly)

Returns the value of attribute from.



11
12
13
# File 'lib/switest/call.rb', line 11

def from
  @from
end

#headersObject (readonly)

Returns the value of attribute headers.



11
12
13
# File 'lib/switest/call.rb', line 11

def headers
  @headers
end

#idObject (readonly)

Returns the value of attribute id.



11
12
13
# File 'lib/switest/call.rb', line 11

def id
  @id
end

#start_timeObject (readonly)

Returns the value of attribute start_time.



12
13
14
# File 'lib/switest/call.rb', line 12

def start_time
  @start_time
end

#stateObject (readonly)

Returns the value of attribute state.



13
14
15
# File 'lib/switest/call.rb', line 13

def state
  @state
end

#toObject (readonly)

Returns the value of attribute to.



11
12
13
# File 'lib/switest/call.rb', line 11

def to
  @to
end

Instance Method Details

#active?Boolean

Returns:

  • (Boolean)


40
41
42
# File 'lib/switest/call.rb', line 40

def active?
  @state == :answered
end

#alive?Boolean

State queries

Returns:

  • (Boolean)


36
37
38
# File 'lib/switest/call.rb', line 36

def alive?
  @state != :ended
end

#answer(wait: 5) ⇒ Object

Actions



61
62
63
64
65
66
# File 'lib/switest/call.rb', line 61

def answer(wait: 5)
  return unless @state == :ringing && inbound?
  sendmsg("execute", "answer")
  return unless wait
  wait_for_answer(timeout: wait)
end

#answered?Boolean

Returns:

  • (Boolean)


44
45
46
# File 'lib/switest/call.rb', line 44

def answered?
  @state == :answered
end

#ended?Boolean

Returns:

  • (Boolean)


48
49
50
# File 'lib/switest/call.rb', line 48

def ended?
  @state == :ended
end

#flush_dtmfObject



93
94
95
# File 'lib/switest/call.rb', line 93

def flush_dtmf
  @dtmf_queue.dequeue until @dtmf_queue.empty?
end

#handle_callstate(call_state) ⇒ Object

Internal state updates



149
150
151
152
153
154
155
156
157
158
159
# File 'lib/switest/call.rb', line 149

def handle_callstate(call_state)
  return if @state == :ended
  new_state = CALLSTATE_MAP[call_state]
  return if new_state.nil? || @state == new_state

  @state = new_state
  if new_state == :answered
    @answer_time = Time.now
    @answered_promise.resolve(true)
  end
end

#handle_dtmf(digit) ⇒ Object



179
180
181
# File 'lib/switest/call.rb', line 179

def handle_dtmf(digit)
  @dtmf_queue.enqueue(digit)
end

#handle_event(response) ⇒ Object

Internal: dispatch a librevox Response event to the appropriate handler.



133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/switest/call.rb', line 133

def handle_event(response)
  return unless response.event?

  case response.event
  when "CHANNEL_CALLSTATE"
    handle_callstate(response.content[:channel_call_state])
  when "CHANNEL_HANGUP_COMPLETE"
    cause = response.content[:hangup_cause]
    handle_hangup(cause, response.content)
  when "DTMF"
    digit = response.content[:dtmf_digit]
    handle_dtmf(digit) if digit
  end
end

#handle_hangup(cause, event_content = {}) ⇒ Object



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/switest/call.rb', line 161

def handle_hangup(cause, event_content = {})
  return if @state == :ended
  @state = :ended
  @end_time = Time.now
  @end_reason = cause

  # Merge event data into headers
  if event_content.is_a?(Hash)
    event_content.each do |k, v|
      next if k == :body
      @headers[k] = v.to_s
    end
  end

  @answered_promise.resolve(true)
  @ended_promise.resolve(true)
end

#hangup(cause = "NORMAL_CLEARING", wait: 5) ⇒ Object



68
69
70
71
72
73
# File 'lib/switest/call.rb', line 68

def hangup(cause = "NORMAL_CLEARING", wait: 5)
  return if ended?
  sendmsg("hangup", hangup_cause: cause)
  return unless wait
  wait_for_end(timeout: wait)
end

#inbound?Boolean

Returns:

  • (Boolean)


52
53
54
# File 'lib/switest/call.rb', line 52

def inbound?
  @direction == :inbound
end

#outbound?Boolean

Returns:

  • (Boolean)


56
57
58
# File 'lib/switest/call.rb', line 56

def outbound?
  @direction == :outbound
end

#play_audio(url, wait: true) ⇒ Object



85
86
87
# File 'lib/switest/call.rb', line 85

def play_audio(url, wait: true)
  sendmsg("execute", "playback", url, event_lock: wait)
end

#receive_dtmf(count: 1, timeout: 5) ⇒ Object



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/switest/call.rb', line 97

def receive_dtmf(count: 1, timeout: 5)
  digits = String.new
  deadline = Time.now + timeout

  count.times do
    remaining = deadline - Time.now
    break if remaining <= 0

    Async::Task.current.with_timeout(remaining) do
      digits << @dtmf_queue.dequeue
    end
  rescue Async::TimeoutError
    break
  end

  digits
end

#reject(reason = :decline, wait: 5) ⇒ Object



75
76
77
78
79
80
81
82
83
# File 'lib/switest/call.rb', line 75

def reject(reason = :decline, wait: 5)
  return unless @state == :ringing && inbound?
  cause = case reason
          when :busy then "USER_BUSY"
          when :decline then "CALL_REJECTED"
          else "CALL_REJECTED"
          end
  hangup(cause, wait: wait)
end

#send_dtmf(digits, wait: true) ⇒ Object



89
90
91
# File 'lib/switest/call.rb', line 89

def send_dtmf(digits, wait: true)
  play_audio("tone_stream://d=200;w=250;#{digits}", wait: wait)
end

#wait_for_answer(timeout: 5) ⇒ Object

Blocking waits



116
117
118
119
120
121
122
# File 'lib/switest/call.rb', line 116

def wait_for_answer(timeout: 5)
  return true if answered?
  Async::Task.current.with_timeout(timeout) { @answered_promise.wait }
  answered?
rescue Async::TimeoutError
  answered?
end

#wait_for_end(timeout: 5) ⇒ Object



124
125
126
127
128
129
130
# File 'lib/switest/call.rb', line 124

def wait_for_end(timeout: 5)
  return true if ended?
  Async::Task.current.with_timeout(timeout) { @ended_promise.wait }
  ended?
rescue Async::TimeoutError
  ended?
end