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.
- checksums.yaml +4 -4
- data/.gitignore +55 -0
- data/.rubocop.yml +14 -2
- data/AUTHORS.md +1 -0
- data/CHANGELOG.md +30 -5
- data/Gemfile +4 -18
- data/LICENSE +21 -0
- data/README.md +22 -82
- data/Rakefile +0 -17
- data/examples/relay/inbound_consumer.rb +26 -0
- data/examples/relay/inbound_dial.rb +28 -0
- data/examples/relay/outbound_collect.rb +27 -0
- data/examples/relay/outbound_consumer.rb +22 -0
- data/examples/relay/outbound_record.rb +25 -0
- data/lib/signalwire.rb +13 -0
- data/lib/signalwire/blade.rb +12 -0
- data/lib/signalwire/blade/connection.rb +200 -0
- data/lib/signalwire/blade/event_handler.rb +15 -0
- data/lib/signalwire/blade/message.rb +38 -0
- data/lib/signalwire/blade/message/connect.rb +18 -0
- data/lib/signalwire/blade/message/execute.rb +16 -0
- data/lib/signalwire/blade/message/subscribe.rb +15 -0
- data/lib/signalwire/common.rb +6 -0
- data/lib/signalwire/logger.rb +27 -0
- data/lib/signalwire/relay.rb +40 -0
- data/lib/signalwire/relay/calling.rb +82 -0
- data/lib/signalwire/relay/calling/action.rb +16 -0
- data/lib/signalwire/relay/calling/action/connect_action.rb +11 -0
- data/lib/signalwire/relay/calling/action/play_action.rb +15 -0
- data/lib/signalwire/relay/calling/action/prompt_action.rb +15 -0
- data/lib/signalwire/relay/calling/action/record_action.rb +15 -0
- data/lib/signalwire/relay/calling/call.rb +210 -0
- data/lib/signalwire/relay/calling/call_convenience_methods.rb +79 -0
- data/lib/signalwire/relay/calling/component.rb +115 -0
- data/lib/signalwire/relay/calling/component/answer.rb +27 -0
- data/lib/signalwire/relay/calling/component/await.rb +20 -0
- data/lib/signalwire/relay/calling/component/connect.rb +45 -0
- data/lib/signalwire/relay/calling/component/dial.rb +34 -0
- data/lib/signalwire/relay/calling/component/hangup.rb +41 -0
- data/lib/signalwire/relay/calling/component/play.rb +46 -0
- data/lib/signalwire/relay/calling/component/prompt.rb +62 -0
- data/lib/signalwire/relay/calling/component/record.rb +53 -0
- data/lib/signalwire/relay/calling/control_component.rb +35 -0
- data/lib/signalwire/relay/calling/result.rb +16 -0
- data/lib/signalwire/relay/calling/result/answer_result.rb +6 -0
- data/lib/signalwire/relay/calling/result/connect_result.rb +9 -0
- data/lib/signalwire/relay/calling/result/dial_result.rb +7 -0
- data/lib/signalwire/relay/calling/result/hangup_result.rb +7 -0
- data/lib/signalwire/relay/calling/result/play_result.rb +6 -0
- data/lib/signalwire/relay/calling/result/prompt_result.rb +11 -0
- data/lib/signalwire/relay/calling/result/record_result.rb +7 -0
- data/lib/signalwire/relay/client.rb +147 -0
- data/lib/signalwire/relay/constants.rb +109 -0
- data/lib/signalwire/relay/consumer.rb +88 -0
- data/lib/signalwire/relay/event.rb +41 -0
- data/lib/signalwire/relay/request.rb +6 -0
- data/lib/signalwire/rest/client.rb +5 -5
- data/lib/signalwire/sdk/fax_response.rb +3 -3
- data/lib/signalwire/sdk/messaging_response.rb +0 -2
- data/lib/signalwire/sdk/twilio_set_fax.rb +9 -8
- data/lib/signalwire/sdk/twilio_set_host.rb +8 -3
- data/lib/signalwire/version.rb +5 -0
- data/signalwire.gemspec +36 -106
- metadata +173 -76
- data/LICENSE.txt +0 -20
- data/VERSION +0 -1
- data/spec/signalwire/rest/client_spec.rb +0 -30
- data/spec/signalwire/rest/integration_spec.rb +0 -102
- data/spec/signalwire/sdk/configuration_spec.rb +0 -28
- data/spec/signalwire/sdk/fax_response_spec.rb +0 -26
- data/spec/signalwire/sdk/messaging_response_spec.rb +0 -18
- data/spec/signalwire/sdk/voice_response_spec.rb +0 -20
- data/spec/signalwire/sdk_spec.rb +0 -27
- data/spec/spec_helper.rb +0 -119
- data/spec/vcr_cassettes/accounts.yml +0 -27
- data/spec/vcr_cassettes/applications.yml +0 -29
- data/spec/vcr_cassettes/get_fax.yml +0 -25
- data/spec/vcr_cassettes/get_fax_media_instance.yml +0 -27
- data/spec/vcr_cassettes/get_fax_media_list.yml +0 -47
- data/spec/vcr_cassettes/list_faxes.yml +0 -25
- data/spec/vcr_cassettes/local_numbers.yml +0 -26
- data/spec/vcr_cassettes/recordings.yml +0 -27
- data/spec/vcr_cassettes/send_fax.yml +0 -27
- data/spec/vcr_cassettes/toll_free_numbers.yml +0 -34
- data/spec/vcr_cassettes/transcriptions.yml +0 -28
|
@@ -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
|
|
@@ -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
|
-
|
|
7
|
-
|
|
8
|
-
unless
|
|
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 =
|
|
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(
|
|
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
|
-
|