signalwire 1.4.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +55 -0
  3. data/.rubocop.yml +14 -2
  4. data/AUTHORS.md +1 -0
  5. data/CHANGELOG.md +30 -5
  6. data/Gemfile +4 -18
  7. data/LICENSE +21 -0
  8. data/README.md +22 -82
  9. data/Rakefile +0 -17
  10. data/examples/relay/inbound_consumer.rb +26 -0
  11. data/examples/relay/inbound_dial.rb +28 -0
  12. data/examples/relay/outbound_collect.rb +27 -0
  13. data/examples/relay/outbound_consumer.rb +22 -0
  14. data/examples/relay/outbound_record.rb +25 -0
  15. data/lib/signalwire.rb +13 -0
  16. data/lib/signalwire/blade.rb +12 -0
  17. data/lib/signalwire/blade/connection.rb +200 -0
  18. data/lib/signalwire/blade/event_handler.rb +15 -0
  19. data/lib/signalwire/blade/message.rb +38 -0
  20. data/lib/signalwire/blade/message/connect.rb +18 -0
  21. data/lib/signalwire/blade/message/execute.rb +16 -0
  22. data/lib/signalwire/blade/message/subscribe.rb +15 -0
  23. data/lib/signalwire/common.rb +6 -0
  24. data/lib/signalwire/logger.rb +27 -0
  25. data/lib/signalwire/relay.rb +40 -0
  26. data/lib/signalwire/relay/calling.rb +82 -0
  27. data/lib/signalwire/relay/calling/action.rb +16 -0
  28. data/lib/signalwire/relay/calling/action/connect_action.rb +11 -0
  29. data/lib/signalwire/relay/calling/action/play_action.rb +15 -0
  30. data/lib/signalwire/relay/calling/action/prompt_action.rb +15 -0
  31. data/lib/signalwire/relay/calling/action/record_action.rb +15 -0
  32. data/lib/signalwire/relay/calling/call.rb +210 -0
  33. data/lib/signalwire/relay/calling/call_convenience_methods.rb +79 -0
  34. data/lib/signalwire/relay/calling/component.rb +115 -0
  35. data/lib/signalwire/relay/calling/component/answer.rb +27 -0
  36. data/lib/signalwire/relay/calling/component/await.rb +20 -0
  37. data/lib/signalwire/relay/calling/component/connect.rb +45 -0
  38. data/lib/signalwire/relay/calling/component/dial.rb +34 -0
  39. data/lib/signalwire/relay/calling/component/hangup.rb +41 -0
  40. data/lib/signalwire/relay/calling/component/play.rb +46 -0
  41. data/lib/signalwire/relay/calling/component/prompt.rb +62 -0
  42. data/lib/signalwire/relay/calling/component/record.rb +53 -0
  43. data/lib/signalwire/relay/calling/control_component.rb +35 -0
  44. data/lib/signalwire/relay/calling/result.rb +16 -0
  45. data/lib/signalwire/relay/calling/result/answer_result.rb +6 -0
  46. data/lib/signalwire/relay/calling/result/connect_result.rb +9 -0
  47. data/lib/signalwire/relay/calling/result/dial_result.rb +7 -0
  48. data/lib/signalwire/relay/calling/result/hangup_result.rb +7 -0
  49. data/lib/signalwire/relay/calling/result/play_result.rb +6 -0
  50. data/lib/signalwire/relay/calling/result/prompt_result.rb +11 -0
  51. data/lib/signalwire/relay/calling/result/record_result.rb +7 -0
  52. data/lib/signalwire/relay/client.rb +147 -0
  53. data/lib/signalwire/relay/constants.rb +109 -0
  54. data/lib/signalwire/relay/consumer.rb +88 -0
  55. data/lib/signalwire/relay/event.rb +41 -0
  56. data/lib/signalwire/relay/request.rb +6 -0
  57. data/lib/signalwire/rest/client.rb +5 -5
  58. data/lib/signalwire/sdk/fax_response.rb +3 -3
  59. data/lib/signalwire/sdk/messaging_response.rb +0 -2
  60. data/lib/signalwire/sdk/twilio_set_fax.rb +9 -8
  61. data/lib/signalwire/sdk/twilio_set_host.rb +8 -3
  62. data/lib/signalwire/version.rb +5 -0
  63. data/signalwire.gemspec +36 -106
  64. metadata +173 -76
  65. data/LICENSE.txt +0 -20
  66. data/VERSION +0 -1
  67. data/spec/signalwire/rest/client_spec.rb +0 -30
  68. data/spec/signalwire/rest/integration_spec.rb +0 -102
  69. data/spec/signalwire/sdk/configuration_spec.rb +0 -28
  70. data/spec/signalwire/sdk/fax_response_spec.rb +0 -26
  71. data/spec/signalwire/sdk/messaging_response_spec.rb +0 -18
  72. data/spec/signalwire/sdk/voice_response_spec.rb +0 -20
  73. data/spec/signalwire/sdk_spec.rb +0 -27
  74. data/spec/spec_helper.rb +0 -119
  75. data/spec/vcr_cassettes/accounts.yml +0 -27
  76. data/spec/vcr_cassettes/applications.yml +0 -29
  77. data/spec/vcr_cassettes/get_fax.yml +0 -25
  78. data/spec/vcr_cassettes/get_fax_media_instance.yml +0 -27
  79. data/spec/vcr_cassettes/get_fax_media_list.yml +0 -47
  80. data/spec/vcr_cassettes/list_faxes.yml +0 -25
  81. data/spec/vcr_cassettes/local_numbers.yml +0 -26
  82. data/spec/vcr_cassettes/recordings.yml +0 -27
  83. data/spec/vcr_cassettes/send_fax.yml +0 -27
  84. data/spec/vcr_cassettes/toll_free_numbers.yml +0 -34
  85. data/spec/vcr_cassettes/transcriptions.yml +0 -28
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Signalwire::Relay::Calling
6
+ class Action
7
+ extend Forwardable
8
+ attr_reader :component
9
+
10
+ def_delegators :@component, :control_id, :payload, :completed, :state
11
+
12
+ def initialize(component:)
13
+ @component = component
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Signalwire::Relay::Calling
6
+ class ConnectAction < Action
7
+ def result
8
+ ConnectResult.new(@component)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Signalwire::Relay::Calling
6
+ class PlayAction < Action
7
+ def result
8
+ PlayResult.new(@component)
9
+ end
10
+
11
+ def stop
12
+ @component.stop
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Signalwire::Relay::Calling
6
+ class PromptAction < Action
7
+ def result
8
+ PromptResult.new(@component)
9
+ end
10
+
11
+ def stop
12
+ @component.stop
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Signalwire::Relay::Calling
6
+ class RecordAction < Action
7
+ def result
8
+ RecordResult.new(@component)
9
+ end
10
+
11
+ def stop
12
+ @component.stop
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,210 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Signalwire::Relay::Calling
6
+ class Call
7
+ include Signalwire::Logger
8
+ include Signalwire::Common
9
+ include Signalwire::Blade::EventHandler
10
+ include Signalwire::Relay::Calling::CallConvenienceMethods
11
+ extend Forwardable
12
+
13
+ attr_reader :device, :type, :node_id, :context, :from, :to,
14
+ :timeout, :tag, :client, :state, :previous_state, :components,
15
+ :busy, :failed
16
+ def_delegators :@client, :relay_execute
17
+
18
+ def self.from_event(client, event)
19
+ new(client, event.call_params)
20
+ end
21
+
22
+ def initialize(client, call_options)
23
+ @client = client
24
+
25
+ @id = call_options[:call_id]
26
+ setup_call_options(call_options)
27
+ @type = @device[:type]
28
+
29
+ @from = @device[:params][:from_number]
30
+ @to = @device[:params][:to_number]
31
+ @timeout = @device[:params][:timeout] || 30
32
+ @tag = SecureRandom.uuid
33
+
34
+ @components = []
35
+
36
+ setup_call_event_handlers
37
+ end
38
+
39
+ def setup_call_event_handlers
40
+ @client.on(:event, proc { |evt| call_match_event(evt) }) do |event|
41
+ case event.event_type
42
+ when 'calling.call.connect'
43
+ change_connect_state(event.call_params[:connect_state])
44
+ when 'calling.call.state'
45
+ change_call_state(event.event_params)
46
+ end
47
+
48
+ broadcast :event, event
49
+ broadcast :state_change, event
50
+ end
51
+ end
52
+
53
+ def change_call_state(event_params)
54
+ call_state = event_params[:params]
55
+ @previous_state = @state
56
+ @state = call_state[:call_state]
57
+ broadcast :call_state_change, previous_state: @previous_state, state: @state
58
+
59
+ update_call_fields(call_state)
60
+ broadcast @state.to_sym, previous_state: @previous_state, state: @state
61
+ finish_call(event_params) if @state == Relay::CallState::ENDED
62
+ end
63
+
64
+ def update_call_fields(call_state)
65
+ @id = call_state[:call_id] if call_state[:call_id]
66
+ @node_id = call_state[:node_id] if call_state[:node_id]
67
+ end
68
+
69
+ def change_connect_state(new_connect_state)
70
+ @previous_connect_state = @connect_state
71
+ @connect_state = new_connect_state
72
+ broadcast :connect_state_change, previous_state: @previous_connect_state, state: @connect_state
73
+ end
74
+
75
+ def call_match_event(event)
76
+ event.event_type.match(/calling\.call/) &&
77
+ !event.event_type.match(/receive/) &&
78
+ (event.call_id == id || event.call_params[:tag] == tag)
79
+ end
80
+
81
+ def id
82
+ @id ||= SecureRandom.uuid
83
+ end
84
+
85
+ def answered?
86
+ @state == 'answered'
87
+ end
88
+
89
+ def ended?
90
+ @state == 'ending' || @state == 'ended'
91
+ end
92
+
93
+ def active?
94
+ !ended?
95
+ end
96
+
97
+ def answer
98
+ answer_component = Signalwire::Relay::Calling::Answer.new(call: self)
99
+ answer_component.wait_for(Relay::CallState::ANSWERED, Relay::CallState::ENDING, Relay::CallState::ENDED)
100
+ AnswerResult.new(component: answer_component)
101
+ end
102
+
103
+ def play(play_object)
104
+ play_component = Signalwire::Relay::Calling::Play.new(call: self, play: play_object)
105
+ play_component.wait_for(Relay::CallPlayState::FINISHED, Relay::CallPlayState::ERROR)
106
+ PlayResult.new(component: play_component)
107
+ end
108
+
109
+ def play!(play_object)
110
+ play_component = Signalwire::Relay::Calling::Play.new(call: self, play: play_object)
111
+ play_component.execute
112
+ PlayAction.new(component: play_component)
113
+ end
114
+
115
+ def prompt(collect_object, play_object)
116
+ component = Prompt.new(call: self, collect: collect_object, play: play_object)
117
+ component.wait_for(Relay::CallPromptState::ERROR, Relay::CallPromptState::NO_INPUT,
118
+ Relay::CallPromptState::NO_MATCH, Relay::CallPromptState::DIGIT,
119
+ Relay::CallPromptState::SPEECH)
120
+ PromptResult.new(component: component)
121
+ end
122
+
123
+ def prompt!(collect_object, play_object)
124
+ component = Prompt,new(call: self, collect: collect_object, play: play_object)
125
+ component.execute
126
+ PromptAction.new(component: component)
127
+ end
128
+
129
+ def connect(devices_object)
130
+ component = Connect.new(call: self, devices: devices_object)
131
+ component.wait_for(Relay::CallConnectState::CONNECTED, Relay::CallConnectState::FAILED)
132
+ ConnectResult.new(component: component)
133
+ end
134
+
135
+ def connect!(devices_object)
136
+ component = Connect.new(call: self, devices: devices_object)
137
+ component.execute
138
+ ConnectAction.new(component: component)
139
+ end
140
+
141
+ def record(record_object)
142
+ component = Record.new(call: self, record: record_object)
143
+ component.wait_for(Relay::CallRecordState::NO_INPUT, Relay::CallRecordState::FINISHED)
144
+ RecordResult.new(component: component)
145
+ end
146
+
147
+ def record!(record_object)
148
+ component = Record.new(call: self, record: record_object)
149
+ component.execute
150
+ RecordAction.new(component: component)
151
+ end
152
+
153
+ def hangup(reason = 'hangup')
154
+ hangup_component = Signalwire::Relay::Calling::Hangup.new(call: self, reason: reason)
155
+ hangup_component.wait_for(Relay::CallState::ENDED)
156
+ HangupResult.new(component: hangup_component)
157
+ end
158
+
159
+ def dial
160
+ dial_component = Signalwire::Relay::Calling::Dial.new(call: self)
161
+ dial_component.wait_for(Relay::CallState::ANSWERED, Relay::CallState::ENDING, Relay::CallState::ENDED)
162
+ DialResult.new(component: dial_component)
163
+ end
164
+
165
+ def wait_for(*events)
166
+ events = [Relay::CallState::ENDED] if events.empty?
167
+
168
+ current_state_index = Relay::CALL_STATES.find_index(@state)
169
+ max_index = events.map { |evt| Relay::CALL_STATES.find_index(evt) }.max
170
+
171
+ return true if current_state_index >= max_index
172
+
173
+ component = Await.new(call: self)
174
+ component.wait_for(*events)
175
+ component.successful
176
+ end
177
+
178
+ def register_component(component)
179
+ @components << component
180
+ end
181
+
182
+ def terminate_components(params = {})
183
+ @components.each do |comp|
184
+ comp.terminate(params) unless component.completed
185
+ end
186
+ end
187
+
188
+ def finish_call(params)
189
+ terminate_components(params)
190
+ client.calling.end_call(id)
191
+ @busy = true if params[:reason] == Relay::DisconnectReason::BUSY
192
+ @failed = true if params[:reason] == Relay::DisconnectReason::FAILED
193
+ broadcast :ended, previous_state: @previous_state, state: @state
194
+ end
195
+
196
+ private
197
+
198
+ def setup_call_options(call_options)
199
+ @node_id = call_options[:node_id]
200
+ @context = call_options[:context]
201
+ @previous_state = nil
202
+ @state = call_options[:call_state]
203
+ @previous_connect_state = nil
204
+ @connect_state = call_options[:connect_state]
205
+ @device = call_options[:device]
206
+ @busy = false
207
+ @failed = false
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,79 @@
1
+ module Signalwire::Relay::Calling
2
+ module CallConvenienceMethods
3
+ def play_audio(url)
4
+ play audio_payload(url)
5
+ end
6
+
7
+ def play_audio!(url)
8
+ play! audio_payload(url)
9
+ end
10
+
11
+ def play_silence(duration)
12
+ play silence_payload(duration)
13
+ end
14
+
15
+ def play_silence!(duration)
16
+ play! silence_payload(duration)
17
+ end
18
+
19
+ def play_tts(sentence, language='en-US', gender='female')
20
+ play tts_payload(sentence, language, gender)
21
+ end
22
+
23
+ def play_tts!(sentence, language='en-US', gender='female')
24
+ play! tts_payload(sentence, language, gender)
25
+ end
26
+
27
+ def prompt_audio(collect, url)
28
+ prompt(collect, audio_payload(url))
29
+ end
30
+
31
+ def prompt_audio!(collect, url)
32
+ prompt!(collect, audio_payload(url))
33
+ end
34
+
35
+ def prompt_silence(collect, duration)
36
+ prompt(collect, silence_payload(duration))
37
+ end
38
+
39
+ def prompt_silence!(collect, url)
40
+ prompt!(collect, silence_payload(duration))
41
+ end
42
+
43
+ def prompt_tts(collect, sentence, language='en-US', gender='female')
44
+ prompt(collect, tts_payload(sentence, language, gender))
45
+ end
46
+
47
+ def prompt_tts!(collect, sentence, language='en-US', gender='female')
48
+ prompt!(collect, tts_payload(sentence, language, gender))
49
+ end
50
+
51
+ def wait_for_ringing
52
+ wait_for(Relay::CallState::RINGING)
53
+ end
54
+
55
+ def wait_for_answered
56
+ wait_for(Relay::CallState::ANSWERED)
57
+ end
58
+
59
+ def wait_for_ending
60
+ wait_for(Relay::CallState::ENDING)
61
+ end
62
+
63
+ def wait_for_ended
64
+ wait_for(Relay::CallState::ENDED)
65
+ end
66
+
67
+ def audio_payload(url)
68
+ [{ type: "audio", params: { url: url } }]
69
+ end
70
+
71
+ def silence_payload(duration)
72
+ [{ type: "silence", params: { duration: duration } }]
73
+ end
74
+
75
+ def tts_payload(sentence, language='en-US', gender='male')
76
+ [{ "type": 'tts', "params": { "text": sentence, "language": language, "gender": gender } }]
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Signalwire::Relay::Calling
4
+ class Component
5
+ attr_reader :completed, :state, :successful, :event, :execute_result, :call
6
+
7
+ def initialize(call:)
8
+ @call = call
9
+ @completed = false
10
+ @successful = false
11
+
12
+ @call.register_component(self)
13
+ end
14
+
15
+ # Relay method corresponding to the command
16
+ #
17
+ def method
18
+ raise NotImplementedError, 'Define this method on the child classes'
19
+ end
20
+
21
+ # Relay payload
22
+ # Implement this on child classes
23
+ def payload
24
+ nil
25
+ end
26
+
27
+ # The event type the subclass listens to
28
+ def event_type
29
+ raise NotImplementedError, 'Define this method on the child classes'
30
+ end
31
+
32
+ def execute_params(method_suffix = nil)
33
+ {
34
+ protocol: @call.client.protocol,
35
+ method: method + method_suffix.to_s,
36
+ params: inner_params
37
+ }
38
+ end
39
+
40
+ def inner_params
41
+ result = {
42
+ node_id: @call.node_id,
43
+ call_id: @call.id
44
+ }
45
+ result[:params] = payload if payload
46
+ result
47
+ end
48
+
49
+ def execute
50
+ setup_handlers
51
+ @call.relay_execute execute_params do |event, outcome|
52
+ handle_execute_result(event, outcome)
53
+ end
54
+ end
55
+
56
+ def setup_handlers
57
+ @call.on :event, event_type: event_type do |evt|
58
+ notification_handler(evt)
59
+ end
60
+ end
61
+
62
+ def handle_execute_result(event, outcome)
63
+ @execute_result = event
64
+ terminate if outcome == :failure
65
+ end
66
+
67
+ def terminate(event = nil)
68
+ @completed = true
69
+ @successful = false
70
+ @state = 'failed'
71
+ @event = event if event
72
+ blocker&.reject
73
+ end
74
+
75
+ def setup_waiting_events(events)
76
+ @events_waiting = events
77
+ end
78
+
79
+ def wait_for(*events)
80
+ setup_waiting_events(events)
81
+ execute
82
+ wait_on_blocker unless @completed
83
+ end
84
+
85
+ def wait_on_blocker
86
+ create_blocker
87
+ blocker.wait
88
+ end
89
+
90
+ # This is the most important method to implement in a subclass
91
+ #
92
+ def notification_handler(_event)
93
+ # to be implemented by subclasses. An example could be:
94
+ #
95
+ # if event.call_params[:call_state] == 'ended'
96
+ # unblock
97
+ # end
98
+ raise NotImplementedError, 'Define this method on the child classes'
99
+ end
100
+
101
+ def create_blocker
102
+ @blocker = Concurrent::Promises.resolvable_future
103
+ end
104
+
105
+ def unblock(value)
106
+ blocker&.resolve value
107
+ end
108
+
109
+ attr_reader :blocker
110
+
111
+ def check_for_waiting_events
112
+ unblock(event) if @events_waiting.include?(@state)
113
+ end
114
+ end
115
+ end