Class: Freeswitch::ESL::Client
Overview
Inbound ESL client: connects to FreeSWITCH mod_event_socket.
Constant Summary
collapse
- DEFAULT_EVENTS =
[
"CHANNEL_CREATE",
"CHANNEL_STATE",
"CHANNEL_CALLSTATE",
"CHANNEL_ANSWER",
"CHANNEL_BRIDGE",
"CHANNEL_UNBRIDGE",
"CHANNEL_HANGUP",
"CHANNEL_HANGUP_COMPLETE",
"CHANNEL_DESTROY",
"CHANNEL_EXECUTE",
"CHANNEL_EXECUTE_COMPLETE",
"CHANNEL_PROGRESS",
"CHANNEL_PROGRESS_MEDIA",
"BACKGROUND_JOB",
"DTMF",
"PLAYBACK_START",
"PLAYBACK_STOP",
"RECORD_START",
"RECORD_STOP",
"HEARTBEAT",
"CUSTOM"
].freeze
Class Method Summary
collapse
Instance Method Summary
collapse
Methods included from Logger
default_logger, #logger
Methods inherited from Connection
#bgapi, #closed?, #filter, #on, #pending_bgapi_command_uuids, #pending_commands_count, #send_command, #subscribe, #unsubscribe
Constructor Details
#initialize(freeswitch: {}, logger: nil, debug: false) ⇒ Client
rubocop:disable Lint/MissingSuper
60
61
62
63
64
65
66
67
68
|
# File 'lib/freeswitch/esl/client.rb', line 60
def initialize(freeswitch: {}, logger: nil, debug: false) @reconnect_handlers = []
@intentionally_closed = false
@ready = false
@mutex = Mutex.new
@ready_cv = ConditionVariable.new
configure(freeswitch:, logger:, debug:)
end
|
Class Method Details
.connect ⇒ Object
44
45
46
|
# File 'lib/freeswitch/esl/client.rb', line 44
def connect(**)
new(**).connect
end
|
Instance Method Details
#attempt_reconnect(sync: false) ⇒ Object
174
175
176
177
178
179
180
181
182
183
184
185
186
|
# File 'lib/freeswitch/esl/client.rb', line 174
def attempt_reconnect(sync: false)
return reconnect_with_backoff if sync
return if @reconnect_thread&.alive?
@reconnect_thread = Thread.new do
reconnect_with_backoff
end
@reconnect_thread.name = "esl-reconnect"
end
|
#authenticate! ⇒ Object
Authenticate against FreeSWITCH after the initial auth/request message.
142
143
144
145
146
147
148
149
150
151
152
153
154
155
|
# File 'lib/freeswitch/esl/client.rb', line 142
def authenticate!
logger.debug "Waiting for auth/request from FreeSWITCH..."
wait_for_auth_request(timeout: 10)
cmd = send_command("auth #{config.freeswitch.password}", timeout: 10).wait(raise_error: false)
return if cmd.ok?
raise AuthenticationError,
"Authentication failed: #{cmd.error.class} - #{cmd.error.message.inspect}"
end
|
#build_socket ⇒ Object
135
136
137
138
139
|
# File 'lib/freeswitch/esl/client.rb', line 135
def build_socket
socket = TCPSocket.new(config.freeswitch.host, config.freeswitch.port)
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
socket
end
|
#close ⇒ Object
93
94
95
96
97
98
99
100
101
102
103
104
|
# File 'lib/freeswitch/esl/client.rb', line 93
def close
@mutex.synchronize { @ready = false }
logger.info "Closing FreeSWITCH ESL client connection"
@intentionally_closed = true
if @reconnect_thread
@reconnect_thread.kill
@reconnect_thread.join(0.1)
end
super
end
|
53
54
55
56
57
58
|
# File 'lib/freeswitch/esl/client.rb', line 53
def configure(**)
close if instance_variable_defined?(:@socket) && @socket && !closed?
config.update(**)
@intentionally_closed = false
self
end
|
#connect ⇒ Object
70
71
72
73
74
75
|
# File 'lib/freeswitch/esl/client.rb', line 70
def connect
@intentionally_closed = false
config.freeswitch.reconnect ? attempt_reconnect(sync: true) : establish_connection
self
end
|
#establish_connection ⇒ Object
126
127
128
129
130
131
132
133
|
# File 'lib/freeswitch/esl/client.rb', line 126
def establish_connection
logger.info "Connecting to FreeSWITCH ESL at #{config.freeswitch.host}:#{config.freeswitch.port}"
initialize_socket(build_socket, debug: config.debug)
authenticate!
subscribe(*DEFAULT_EVENTS)
ready!
logger.info "FreeSWITCH ESL client authenticated and ready!"
end
|
#exec(command, timeout: Command::DEFAULT_TIMEOUT, raise_error: true) {|cmd| ... } ⇒ Command
119
120
121
122
123
124
|
# File 'lib/freeswitch/esl/client.rb', line 119
def exec(command, *, timeout: Command::DEFAULT_TIMEOUT, raise_error: true, &)
cmd = Command.new(self, command, *, timeout:, raise_error:, &)
cmd.execute!
cmd
end
|
#on_disconnect(error) ⇒ Object
162
163
164
165
166
167
168
169
170
171
172
|
# File 'lib/freeswitch/esl/client.rb', line 162
def on_disconnect(error)
super
@mutex.synchronize { @ready = false }
return if @intentionally_closed
logger.warn "Disconnected from FreeSWITCH ESL: #{error.message}"
attempt_reconnect unless @intentionally_closed
end
|
#on_reconnect(&block) ⇒ Object
106
107
108
109
110
|
# File 'lib/freeswitch/esl/client.rb', line 106
def on_reconnect(&block)
logger.debug "Registering FreeSWITCH ESL client reconnect handler"
@reconnect_handlers << block
self
end
|
#ready! ⇒ Object
157
158
159
160
|
# File 'lib/freeswitch/esl/client.rb', line 157
def ready!
@mutex.synchronize { @ready = true }
@ready_cv.broadcast
end
|
#ready? ⇒ Boolean
89
90
91
|
# File 'lib/freeswitch/esl/client.rb', line 89
def ready?
@ready
end
|
#reconnect_once ⇒ Object
204
205
206
207
208
209
210
211
212
|
# File 'lib/freeswitch/esl/client.rb', line 204
def reconnect_once
establish_connection
@reconnect_handlers.each(&:call)
true
rescue StandardError => e
logger.warn "Reconnect attempt failed: #{e.message}"
false
end
|
#reconnect_with_backoff ⇒ Object
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
|
# File 'lib/freeswitch/esl/client.rb', line 188
def reconnect_with_backoff
retries = 0
delay = config.freeswitch.retry_delay
loop do
break if @intentionally_closed
break if reconnect_once
sleep(delay)
retries += 1
break if stop_reconnect?(retries)
end
end
|
#stop_reconnect?(retries) ⇒ Boolean
214
215
216
|
# File 'lib/freeswitch/esl/client.rb', line 214
def stop_reconnect?(retries)
retries >= config.freeswitch.max_retries || @intentionally_closed
end
|
#wait_until_ready(timeout: nil, raise_error: true) ⇒ Object
77
78
79
80
81
82
83
84
85
86
87
|
# File 'lib/freeswitch/esl/client.rb', line 77
def wait_until_ready(timeout: nil, raise_error: true)
@mutex.synchronize do
return self if ready?
@ready_cv.wait(@mutex, timeout)
raise TimeoutError, "Timed out waiting for FreeSWITCH ESL client to be ready" if raise_error && !ready?
end
self
end
|