signalwire 1.4.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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