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,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
-