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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Signalwire::Relay::Calling
4
+ class RecordResult < Result
5
+ def_delegators :@component, :url, :duration, :size
6
+ end
7
+ end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Signalwire::Relay
4
+ class Client
5
+ include Signalwire::Logger
6
+ include Signalwire::Common
7
+ include Signalwire::Blade::EventHandler
8
+
9
+ attr_accessor :project, :host, :url, :protocol, :connected, :session
10
+
11
+ # Creates a Relay client
12
+ #
13
+ # @param project [String] Your SignalWire project identifier
14
+ # @param token [String] Your SignalWire secret token
15
+ # @param SIGNALWIRE_HOST [String] Your SignalWire space URL (not needed for production usage)
16
+
17
+ def initialize(project:, token:, host: nil)
18
+ @project = project
19
+ @token = token
20
+ @host = host || ENV.fetch('SIGNALWIRE_HOST', Signalwire::Relay::DEFAULT_URL)
21
+ @url = clean_up_space_url(@host)
22
+ @protocol = nil
23
+
24
+ @connected = false
25
+
26
+ setup_session
27
+ setup_handlers
28
+ setup_events
29
+ end
30
+
31
+ # Starts the client connection
32
+ #
33
+ def connect!
34
+ logger.debug "Connecting to #{@space_url}"
35
+ session.connect!
36
+ end
37
+
38
+ # Terminates the session
39
+ #
40
+ def disconnect!
41
+ session.disconnect!
42
+ end
43
+
44
+ def clean_up_space_url(space_url)
45
+ uri = URI.parse(space_url)
46
+ # oddly, URI.parse interprets a simple hostname as a path
47
+ if uri.scheme.nil? && uri.host.nil?
48
+ unless uri.path.nil?
49
+ uri.scheme = 'wss'
50
+ uri.host = uri.path
51
+ uri.path = ''
52
+ end
53
+ end
54
+
55
+ uri.to_s
56
+ end
57
+
58
+ def execute(command, &block)
59
+ @session.execute(command, &block)
60
+ end
61
+
62
+ # TODO: refactor this for style
63
+ def relay_execute(command, timeout = Signalwire::Relay::COMMAND_TIMEOUT, &block)
64
+ promise = Concurrent::Promises.resolvable_future
65
+
66
+ execute(command) do |event|
67
+ promise.fulfill event
68
+ end
69
+
70
+ promise.wait timeout
71
+
72
+ if promise.fulfilled?
73
+ event = promise.value
74
+ code = event.dig(:result, :result, :code)
75
+ message = event.dig(:result, :result, :message)
76
+ success = code == '200' ? :success : :failure
77
+
78
+ if code
79
+ block.call(event, success) if block_given?
80
+ logger.error "Relay command failed with code #{code} and message: #{message}" unless success
81
+ else
82
+ logger.error 'Unknown Relay command failure, result code not found'
83
+ end
84
+ else
85
+ logger.error 'Unknown Relay command failure, command timed out'
86
+ end
87
+ end
88
+
89
+ def calling
90
+ @calling ||= Signalwire::Relay::Calling::Instance.new(self)
91
+ end
92
+
93
+ private
94
+
95
+ def setup_handlers
96
+ @session.on :connected do |event|
97
+ logger.debug 'Relay client connected'
98
+ broadcast :connecting, event
99
+ protocol_setup
100
+ end
101
+ end
102
+
103
+ def protocol_setup
104
+ setup = {
105
+ protocol: 'signalwire',
106
+ method: 'setup',
107
+ params: {
108
+ }
109
+ }
110
+
111
+ # hijack our protocol
112
+ setup[:params][:protocol] = @protocol if @protocol
113
+
114
+ @session.execute(setup) do |event|
115
+ @protocol = event.dig(:result, :result, :protocol)
116
+ logger.debug "Protocol set up as #{protocol}"
117
+
118
+ notification_request = {
119
+ "protocol": @protocol,
120
+ "command": 'add',
121
+ "channels": ['notifications']
122
+ }
123
+
124
+ @session.subscribe(notification_request) do
125
+ logger.debug "Subscribed to notifications for #{protocol}"
126
+ @connected = true
127
+ broadcast :ready, self
128
+ end
129
+ end
130
+ end
131
+
132
+ def setup_session
133
+ auth = {
134
+ project: @project,
135
+ token: @token
136
+ }
137
+ @session = Signalwire::Blade::Connection.new(url: url, authentication: auth)
138
+ end
139
+
140
+ def setup_events
141
+ @session.on :message, %i[\[\] method] => 'blade.broadcast' do |event|
142
+ relay = Signalwire::Relay::Event.from_blade(event)
143
+ broadcast :event, relay
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Signalwire::Relay
4
+ DEFAULT_URL = 'relay.signalwire.com'
5
+ COMMAND_TIMEOUT = 30
6
+ DEFAULT_CALL_TIMEOUT = 30
7
+ PING_TIMEOUT = 5
8
+
9
+ module CallState
10
+ NONE = 'none'
11
+ CREATED = 'created'
12
+ RINGING = 'ringing'
13
+ ANSWERED = 'answered'
14
+ ENDING = 'ending'
15
+ ENDED = 'ended'
16
+ end
17
+
18
+ CALL_STATES = [
19
+ CallState::NONE,
20
+ CallState::CREATED,
21
+ CallState::RINGING,
22
+ CallState::ANSWERED,
23
+ CallState::ENDING,
24
+ CallState::ENDED
25
+ ].freeze
26
+
27
+ module DisconnectReason
28
+ HANGUP = 'hangup'
29
+ CANCEL = 'cancel'
30
+ BUSY = 'busy'
31
+ NO_ANSWER = 'noAnswer'
32
+ DECLINE = 'decline'
33
+ ERROR = 'error'
34
+ end
35
+
36
+ module DisconnectSource
37
+ NONE = 'none'
38
+ CLIENT = 'client'
39
+ SERVER = 'server'
40
+ ENDPOINT = 'endpoint'
41
+ end
42
+
43
+ module CallType
44
+ PHONE = 'phone'
45
+ SIP = 'sip'
46
+ WEBRTC = 'webrtc'
47
+ end
48
+
49
+ module CallConnectState
50
+ DISCONNECTED = 'disconnected'
51
+ CONNECTING = 'connecting'
52
+ CONNECTED = 'connected'
53
+ FAILED = 'failed'
54
+ end
55
+
56
+ module CallNotification
57
+ STATE = 'calling.call.state'
58
+ RECEIVE = 'calling.call.receive'
59
+ CONNECT = 'calling.call.connect'
60
+ RECORD = 'calling.call.record'
61
+ PLAY = 'calling.call.play'
62
+ COLLECT = 'calling.call.collect'
63
+ end
64
+
65
+ CALL_EVENT_STATE_FIELDS = {
66
+ CallNotification::STATE => 'state',
67
+ CallNotification::RECEIVE => 'call_state',
68
+ CallNotification::CONNECT => 'connect_state',
69
+ CallNotification::RECORD => 'state',
70
+ CallNotification::PLAY => 'state',
71
+ CallNotification::COLLECT => 'result' # this actually need to be parsed separately
72
+ }.freeze
73
+
74
+ module CallPlayState
75
+ PLAYING = 'playing'
76
+ ERROR = 'error'
77
+ FINISHED = 'finished'
78
+ end
79
+
80
+ module CallPromptState
81
+ ERROR = 'error'
82
+ NO_INPUT = 'no_input'
83
+ NO_MATCH = 'no_match'
84
+ DIGIT = 'digit'
85
+ SPEECH = 'speech'
86
+ end
87
+
88
+ module CallRecordState
89
+ RECORDING = 'recording'
90
+ NO_INPUT = 'no_input'
91
+ FINISHED = 'finished'
92
+ end
93
+
94
+ module ComponentMethod
95
+ ANSWER = 'call.answer'
96
+ CONNECT = 'call.connect'
97
+ DIAL = 'call.begin' # BEGIN is a reserved word
98
+ HANGUP = 'call.end' # END is a reserved word
99
+ PLAY = 'call.play'
100
+ PROMPT = 'call.play_and_collect'
101
+ RECORD = 'call.record'
102
+ end
103
+
104
+ module CommonState
105
+ SUCCESSFUL = 'successful'
106
+ end
107
+ end
108
+
109
+ Relay = Signalwire::Relay
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Signalwire::Relay
4
+ class Consumer
5
+ include Signalwire::Logger
6
+ attr_reader :client, :project, :token
7
+
8
+ class << self
9
+ def contexts(val = nil)
10
+ if val.nil?
11
+ @contexts || []
12
+ else
13
+ @contexts = val
14
+ end
15
+ end
16
+ end
17
+
18
+ # Creates a Consumer instance ready to be run
19
+ #
20
+ # The initialization parameters can also be supplied via ENV variables
21
+ # (SIGNALWIRE_ACCOUNT, SIGNALWIRE_TOKEN and SIGNALWIRE_HOST)
22
+ # Passed-in values override the environment ones.
23
+ #
24
+ # @param project [String] Your SignalWire project identifier
25
+ # @param token [String] Your SignalWire secret token
26
+ # @param SIGNALWIRE_HOST [String] Your SignalWire space URL (not needed for production usage)
27
+
28
+ def initialize(project: nil, token: nil, host: nil)
29
+ @project = project || ENV['SIGNALWIRE_ACCOUNT']
30
+ @token = token || ENV['SIGNALWIRE_TOKEN']
31
+ @url = host || ENV['SIGNALWIRE_HOST'] || Signalwire::Relay::DEFAULT_URL
32
+ @client = Signalwire::Relay::Client.new(project: @project,
33
+ token: @token, host: @url)
34
+ end
35
+
36
+ def setup
37
+ # do stuff here.
38
+ end
39
+
40
+ def ready
41
+ # do stuff here.
42
+ end
43
+
44
+ def teardown
45
+ # do stuff here.
46
+ end
47
+
48
+ def on_task(task); end
49
+
50
+ def on_event(event)
51
+ # all-events firespout
52
+ end
53
+
54
+ def on_incoming_call(call); end
55
+
56
+ def run
57
+ setup
58
+ client.once :ready do
59
+ ready
60
+ setup_receive_listeners
61
+ setup_all_events_listener
62
+ # not sure if ordering matters
63
+ end
64
+ client.connect!
65
+ end
66
+
67
+ def stop
68
+ teardown
69
+ client.disconnect!
70
+ end
71
+
72
+ private
73
+
74
+ def setup_receive_listeners
75
+ self.class.contexts.each do |cxt|
76
+ client.calling.receive context: cxt do |call|
77
+ on_incoming_call(call)
78
+ end
79
+ end
80
+ end
81
+
82
+ def setup_all_events_listener
83
+ client.on :event do |evt|
84
+ on_event(evt)
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Signalwire::Relay
4
+ class Event < Signalwire::Blade::Message
5
+ def event_type
6
+ dig(:params, :params, :event_type)
7
+ end
8
+
9
+ def name
10
+ event_type
11
+ end
12
+
13
+ def call_id
14
+ dig(:params, :params, :params, :call_id)
15
+ rescue StandardError
16
+ nil
17
+ end
18
+
19
+ def control_id
20
+ dig(:params, :params, :params, :control_id)
21
+ rescue StandardError
22
+ nil
23
+ end
24
+
25
+ def event_params
26
+ dig(:params, :params)
27
+ rescue StandardError
28
+ {}
29
+ end
30
+
31
+ def call_params
32
+ dig(:params, :params, :params)
33
+ rescue StandardError
34
+ {}
35
+ end
36
+
37
+ def self.from_blade(blade_event)
38
+ new(blade_event.payload)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Signalwire::Relay
4
+ class Request < Signalwire::Blade::Execute
5
+ end
6
+ end
@@ -2,12 +2,12 @@
2
2
 
3
3
  module Signalwire::REST
4
4
  class Client < Twilio::REST::Client
5
- def initialize(username=nil, password=nil, account_sid=nil, region=nil, http_client=Twilio::HTTP::Client.new, **args)
6
- signalwire_space_url = args.delete(:signalwire_space_url)
7
-
8
- unless signalwire_space_url.nil?
5
+ def initialize(username = nil, password = nil, account_sid = nil, region = nil, http_client = Twilio::HTTP::Client.new, **args)
6
+ host = args.delete(:SIGNALWIRE_HOST)
7
+
8
+ unless host.nil?
9
9
  Signalwire::Sdk.configure do |config|
10
- config.hostname = signalwire_space_url
10
+ config.hostname = host
11
11
  end
12
12
  end
13
13
 
@@ -1,14 +1,15 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require 'twilio-ruby/twiml/fax_response'
3
4
 
4
5
  module Signalwire::Sdk
5
6
  class FaxResponse < Twilio::TwiML::FaxResponse
6
7
  # Create a new <Reject> element
7
8
  # keyword_args:: additional attributes
8
- def reject(reason: nil, **keyword_args)
9
+ def reject(**keyword_args)
9
10
  append(Reject.new(**keyword_args))
10
11
  end
11
-
12
+
12
13
  # <Leave> TwiML Verb
13
14
  class Reject < ::Twilio::TwiML::TwiML
14
15
  def initialize(**keyword_args)
@@ -20,4 +21,3 @@ module Signalwire::Sdk
20
21
  end
21
22
  end
22
23
  end
23
-
@@ -4,5 +4,3 @@ module Signalwire::Sdk
4
4
  class MessagingResponse < Twilio::TwiML::MessagingResponse
5
5
  end
6
6
  end
7
-
8
-