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,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
4
+ %w[
5
+ bundler/setup
6
+ signalwire
7
+ ].each { |f| require f }
8
+
9
+ # Set logging to debug for testing
10
+ Signalwire::Logger.logger.level = ::Logger::DEBUG
11
+
12
+ class OutboundConsumer < Signalwire::Relay::Consumer
13
+ def ready
14
+ logger.info 'Dialing out'
15
+ call = client.calling.new_call(from: ENV['FROM_NUMBER'], to: ENV['TO_NUMBER'])
16
+ call.dial
17
+ call.play_tts 'please leave your message after the beep. Press pound when done.'
18
+ result = call.record({"audio": { "beep": "true", "terminators": "#"}})
19
+ call.play_tts 'you said:'
20
+ call.play_audio result.url
21
+ call.hangup
22
+ end
23
+ end
24
+
25
+ OutboundConsumer.new.run
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Signalwire
4
+ end
5
+
6
+ require 'signalwire/sdk'
7
+ require 'signalwire/logger'
8
+ require 'signalwire/common'
9
+
10
+ require 'signalwire/blade'
11
+ require 'signalwire/relay'
12
+
13
+
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Signalwire::Blade
4
+ RECONNECT_PERIOD = 5
5
+ end
6
+
7
+ require 'signalwire/blade/event_handler'
8
+ require 'signalwire/blade/connection'
9
+ require 'signalwire/blade/message'
10
+ require 'signalwire/blade/message/connect'
11
+ require 'signalwire/blade/message/execute'
12
+ require 'signalwire/blade/message/subscribe'
@@ -0,0 +1,200 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'has_guarded_handlers'
4
+ require 'eventmachine'
5
+ require 'faye/websocket'
6
+ require 'json'
7
+
8
+ module Signalwire::Blade
9
+ class Connection
10
+ include Signalwire::Logger
11
+ include Signalwire::Blade::EventHandler
12
+ include Signalwire::Common
13
+
14
+ attr_reader :session_id, :connected, :node_id, :connection
15
+
16
+ def initialize(**options)
17
+ @options = options
18
+ @session_id = nil
19
+ @node_id = nil
20
+ @connected = false
21
+ @url = @options.fetch(:url, 'wss://relay.signalwire.com')
22
+ @log_traffic = options.fetch(:log_traffic, true)
23
+ @authentication = options.fetch(:authentication, nil)
24
+
25
+ @inbound_queue = EM::Queue.new
26
+ @outbound_queue = EM::Queue.new
27
+
28
+ @counter = 1
29
+ end
30
+
31
+ def connect!
32
+ setup_started_event
33
+ enable_epoll
34
+ handle_signals
35
+
36
+ main_loop!
37
+ end
38
+
39
+ def reconnect!
40
+ return if @shutdown
41
+ sleep Signalwire::Blade::RECONNECT_PERIOD
42
+ @connected = false
43
+ logger.info "Attempting reconnection"
44
+ main_loop!
45
+ end
46
+
47
+ def main_loop!
48
+ EM.run do
49
+ @ws = Faye::WebSocket::Client.new(@url)
50
+
51
+ @ws.on(:open) { |event| broadcast :started, event }
52
+ @ws.on(:message) { |event| enqueue_inbound event }
53
+ @ws.on(:close) { handle_close }
54
+
55
+ @ws.on :error do |error|
56
+ logger.error "Error occurred: #{error.message}"
57
+ end
58
+
59
+ EM.next_tick { flush_queues }
60
+ end
61
+ end
62
+
63
+ def setup_started_event
64
+ on :started do |_event|
65
+ @connected = true
66
+ myreq = connect_request
67
+ start_periodic_timer
68
+
69
+ write_command(myreq) do |event|
70
+ @session_id = event.dig(:result, :sessionid) unless @session_id
71
+ @node_id = event.dig(:result, :nodeid) unless @node_d
72
+ logger.info "Blade Session connected with id: #{@session_id}"
73
+ broadcast :connected, event
74
+ end
75
+
76
+ rescue StandardError => e
77
+ logger.error e.inspect
78
+ logger.error e.backtrace
79
+ end
80
+ end
81
+
82
+ def enable_epoll
83
+ # This is only enabled on Linux
84
+ EM.epoll
85
+ logger.debug "Running with epoll #{EM.epoll?}"
86
+ end
87
+
88
+ def transmit(message)
89
+ enqueue_outbound message
90
+ end
91
+
92
+ def write(message)
93
+ log_traffic :send, message
94
+ @ws.send(message)
95
+ end
96
+
97
+ def receive(message)
98
+ event = Message.from_json(message.data)
99
+ log_traffic :recv, event.payload
100
+ EM.defer do
101
+ broadcast :message, event
102
+ end
103
+ end
104
+
105
+ def write_command(command, &block)
106
+ once(:message, id: command.id, &block) if block_given?
107
+ transmit(command.build_request.to_json)
108
+ end
109
+
110
+ def execute(params, &block)
111
+ block_given? ? write_command(Execute.new(params), &block) : write_command(Execute.new(params))
112
+ end
113
+
114
+ def subscribe(params, &block)
115
+ block_given? ? write_command(Subscribe.new(params), &block) : write_command(Subscribe.new(params))
116
+ end
117
+
118
+ def handle_close
119
+ reconnect!
120
+ end
121
+
122
+ def disconnect!
123
+ # logger.info 'Stopping Blade event loop'
124
+ @ws = nil
125
+ @connected = false
126
+ EM.stop
127
+ end
128
+
129
+ def flush_queues
130
+ @inbound_queue.pop { |inbound| receive(inbound) } until @inbound_queue.empty?
131
+ if connected?
132
+ @outbound_queue.pop { |outbound| write(outbound) } until @outbound_queue.empty?
133
+ end
134
+
135
+ EM.next_tick { flush_queues }
136
+ end
137
+
138
+ def enqueue_inbound(message)
139
+ @inbound_queue.push message
140
+ end
141
+
142
+ def enqueue_outbound(message)
143
+ @outbound_queue.push message
144
+ end
145
+
146
+ def connect_request
147
+ req = Connect.new
148
+ req[:params][:authentication] = @authentication if @authentication
149
+ req
150
+ end
151
+
152
+ def connected?
153
+ @connected == true
154
+ end
155
+
156
+ def start_periodic_timer
157
+ pinger = EventMachine::PeriodicTimer.new(Signalwire::Relay::PING_TIMEOUT) do
158
+ timeouter = EventMachine::Timer.new(2) do
159
+ # reconnect logic goes here
160
+ logger.error "We got disconnected!"
161
+ pinger.cancel
162
+ reconnect!
163
+ end
164
+
165
+ @ws.ping 'detecting presence' do
166
+ timeouter.cancel
167
+ end
168
+ end
169
+ end
170
+
171
+ def log_traffic(direction, message)
172
+ if @log_traffic
173
+ pretty = case direction
174
+ when :send
175
+ JSON.pretty_generate(JSON.parse(message))
176
+ when :recv
177
+ JSON.pretty_generate(message)
178
+ end
179
+ end
180
+ logger.debug "#{direction.to_s.upcase}: #{pretty}"
181
+ end
182
+
183
+ def handle_signals
184
+ Signal.trap('INT') do
185
+ shutdown_from_signal
186
+ end
187
+
188
+ Signal.trap('TERM') do
189
+ shutdown_from_signal
190
+ end
191
+ end
192
+
193
+
194
+ def shutdown_from_signal
195
+ @shutdown = true
196
+ disconnect!
197
+ exit
198
+ end
199
+ end
200
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'has_guarded_handlers'
4
+
5
+ module Signalwire::Blade
6
+ module EventHandler
7
+ include HasGuardedHandlers
8
+ alias on register_handler
9
+ alias once register_tmp_handler
10
+
11
+ def broadcast(event_type, event)
12
+ trigger_handler event_type, event, broadcast: true
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Signalwire::Blade
6
+ class Message
7
+ extend Forwardable
8
+ def_delegators :@payload, :[], :[]=, :dig
9
+
10
+ def initialize(params = {})
11
+ @payload = params
12
+ @id = params[:id]
13
+ end
14
+
15
+ def id
16
+ @id ||= SecureRandom.uuid
17
+ end
18
+
19
+ def payload
20
+ @payload ||= {}
21
+ end
22
+
23
+ def build_request
24
+ payload.merge(
25
+ jsonrpc: '2.0',
26
+ id: id
27
+ )
28
+ end
29
+
30
+ def self.from_json(json_hash)
31
+ new JSON.parse(json_hash, symbolize_names: true)
32
+ end
33
+
34
+ def to_s
35
+ inspect
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Signalwire::Blade
4
+ class Connect < Message
5
+ def initialize
6
+ @payload = {
7
+ method: 'blade.connect',
8
+ params: {
9
+ version: {
10
+ major: 2,
11
+ minor: 1,
12
+ revision: 0
13
+ }
14
+ }
15
+ }
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Signalwire::Blade
4
+ class Execute < Message
5
+ # Creates an Execute message
6
+ #
7
+ # @param params [Hash] The "params" portion of the execute
8
+ def initialize(params = {})
9
+ @payload =
10
+ {
11
+ method: 'blade.execute',
12
+ params: params
13
+ }
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Signalwire::Blade
4
+ class Subscribe < Message
5
+ # Creates a Subscribe message
6
+ #
7
+ # @param params [Hash] The "params" portion of the message
8
+ def initialize(params = {})
9
+ @payload = {
10
+ method: 'blade.subscription',
11
+ params: params
12
+ }
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Signalwire
4
+ module Common
5
+ end
6
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ module Signalwire
6
+ module Logger
7
+ class << self
8
+ # A global logger object
9
+ # @return [Logger] a Logger instance
10
+ def logger
11
+ @logger ||= begin
12
+ logger = ::Logger.new(STDERR, progname: 'SignalWire', level: ::Logger::DEBUG)
13
+ logger.level = ENV.fetch('SIGNALWIRE_LOG_LEVEL', ::Logger::WARN)
14
+ logger
15
+ end
16
+ end
17
+ end
18
+
19
+ def logger
20
+ Signalwire::Logger.logger
21
+ end
22
+
23
+ def level=(level)
24
+ Signalwire::Logger.logger.level = level
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Signalwire::Relay
4
+ end
5
+
6
+ require 'signalwire/relay/constants'
7
+ require 'signalwire/relay/client'
8
+ require 'signalwire/relay/request'
9
+ require 'signalwire/relay/event'
10
+ require 'signalwire/relay/consumer'
11
+
12
+ require 'signalwire/relay/calling'
13
+ require 'signalwire/relay/calling/call_convenience_methods'
14
+ require 'signalwire/relay/calling/call'
15
+
16
+ require 'signalwire/relay/calling/action'
17
+ require 'signalwire/relay/calling/action/connect_action'
18
+ require 'signalwire/relay/calling/action/play_action'
19
+ require 'signalwire/relay/calling/action/prompt_action'
20
+ require 'signalwire/relay/calling/action/record_action'
21
+
22
+ require 'signalwire/relay/calling/result'
23
+ require 'signalwire/relay/calling/result/answer_result'
24
+ require 'signalwire/relay/calling/result/connect_result'
25
+ require 'signalwire/relay/calling/result/dial_result'
26
+ require 'signalwire/relay/calling/result/hangup_result'
27
+ require 'signalwire/relay/calling/result/play_result'
28
+ require 'signalwire/relay/calling/result/prompt_result'
29
+ require 'signalwire/relay/calling/result/record_result'
30
+
31
+ require 'signalwire/relay/calling/component'
32
+ require 'signalwire/relay/calling/control_component'
33
+ require 'signalwire/relay/calling/component/answer'
34
+ require 'signalwire/relay/calling/component/connect'
35
+ require 'signalwire/relay/calling/component/dial'
36
+ require 'signalwire/relay/calling/component/hangup'
37
+ require 'signalwire/relay/calling/component/play'
38
+ require 'signalwire/relay/calling/component/prompt'
39
+ require 'signalwire/relay/calling/component/record'
40
+ require 'signalwire/relay/calling/component/await'
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+ require 'concurrent-ruby'
5
+
6
+ module Signalwire::Relay
7
+ module Calling
8
+ class Instance
9
+ extend Forwardable
10
+ include Signalwire::Logger
11
+ include Signalwire::Common
12
+
13
+ def_delegators :@client, :relay_execute, :protocol, :on, :once, :broadcast
14
+
15
+ def initialize(client)
16
+ @client = client
17
+ end
18
+
19
+ def calls
20
+ @calls ||= Concurrent::Array.new
21
+ end
22
+
23
+ def contexts
24
+ @contexts ||= Concurrent::Array.new
25
+ end
26
+
27
+ def receive(context:, &block)
28
+ @client.on :event, event_type: 'calling.call.receive' do |event|
29
+ logger.info "Starting up call for #{event.call_params}"
30
+ call_obj = Signalwire::Relay::Calling::Call.from_event(self, event)
31
+ calls << call_obj
32
+ block.call(call_obj) if block_given?
33
+ end
34
+
35
+ receive_command = {
36
+ protocol: protocol,
37
+ method: 'call.receive',
38
+ params: {
39
+ context: context
40
+ }
41
+ }
42
+
43
+ relay_execute receive_command do
44
+ contexts << context
45
+ end
46
+ end
47
+
48
+ def find_call_by_id(call_id)
49
+ calls.find { |call| call.id == call_id }
50
+ end
51
+
52
+ def find_call_by_tag(tag)
53
+ calls.find { |call| call.tag == tag }
54
+ end
55
+
56
+ def end_call(call_id)
57
+ calls.delete find_call_by_id(call_id)
58
+ end
59
+
60
+ def new_call(from:, to:, device_type: 'phone', timeout: 30)
61
+ params = {
62
+ device: {
63
+ type: device_type,
64
+ params: {
65
+ from_number: from,
66
+ to_number: to,
67
+ timeout: timeout
68
+ }
69
+ }
70
+ }
71
+ call = Call.new(self, params)
72
+ calls << call
73
+ call
74
+ end
75
+
76
+ def dial(from:, to:, device_type: 'phone', timeout: 30)
77
+ handle = new_call(from: from, to: to, device_type: device_type, timeout: timeout)
78
+ handle.dial
79
+ end
80
+ end
81
+ end
82
+ end