librevox 0.9 → 1.0.0.alpha1
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 +5 -5
- data/README.md +252 -167
- data/lib/librevox/applications.rb +80 -67
- data/lib/librevox/client.rb +39 -0
- data/lib/librevox/command_socket.rb +12 -26
- data/lib/librevox/commands.rb +23 -23
- data/lib/librevox/listener/base.rb +56 -29
- data/lib/librevox/listener/inbound.rb +40 -15
- data/lib/librevox/listener/outbound.rb +40 -34
- data/lib/librevox/protocol/connection.rb +41 -0
- data/lib/librevox/protocol/response.rb +60 -0
- data/lib/librevox/runner.rb +37 -0
- data/lib/librevox/server.rb +33 -0
- data/lib/librevox/version.rb +5 -0
- data/lib/librevox.rb +32 -42
- metadata +66 -36
- data/Rakefile +0 -6
- data/TODO +0 -29
- data/lib/librevox/response.rb +0 -52
- data/librevox.gemspec +0 -37
- data/spec/helper.rb +0 -86
- data/spec/librevox/listener/spec_inbound.rb +0 -22
- data/spec/librevox/listener/spec_outbound.rb +0 -300
- data/spec/librevox/listener.rb +0 -142
- data/spec/librevox/spec_applications.rb +0 -238
- data/spec/librevox/spec_commands.rb +0 -103
- data/spec/librevox/spec_response.rb +0 -67
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 6d47382ef56ccc15ea2aacf0e4bc5f4bf197fdb7338a25e7841a7f85a96ebe31
|
|
4
|
+
data.tar.gz: 86736dac97c19aaa298693bfe99c3f75a9b829777f37ea20d2932a1f5221dc3b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: efea41f41b6e92608d8dbe69035cbc537c7c1da77c15767c92aa1679633d57c840f26ac37a740f4df3a9ef24a24103bad9bdf2a1ed514bc8d73fd33e0a9576f9
|
|
7
|
+
data.tar.gz: 6a1502164b5df5387b6f1a12c65d379f3fb13caec7a27201c4aa97d1122a22ac667021991802f8158b53e3bd91d2b8b4a0f401709ebee9973331b960bfecff67
|
data/README.md
CHANGED
|
@@ -1,207 +1,292 @@
|
|
|
1
1
|
# Librevox
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
[
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
A Ruby library for interacting with [FreeSWITCH](http://www.freeswitch.org) through [mod_event_socket](https://developer.signalwire.com/freeswitch/FreeSWITCH-Explained/Modules/mod_event_socket_1048924/), using async I/O.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Prerequisites](#prerequisites)
|
|
8
|
+
- [Installation](#installation)
|
|
9
|
+
- [Inbound Listener](#inbound-listener)
|
|
10
|
+
- [Events](#events)
|
|
11
|
+
- [Event Filtering](#event-filtering)
|
|
12
|
+
- [Outbound Listener](#outbound-listener)
|
|
13
|
+
- [Dialplan](#dialplan)
|
|
14
|
+
- [API Commands](#api-commands)
|
|
15
|
+
- [Starting Listeners](#starting-listeners)
|
|
16
|
+
- [Closing Connections](#closing-connections)
|
|
17
|
+
- [Command Socket](#command-socket)
|
|
18
|
+
- [Configuration](#configuration)
|
|
19
|
+
- [Event Socket Protocol](#event-socket-protocol)
|
|
20
|
+
- [Outbound session lifecycle](#outbound-session-lifecycle)
|
|
21
|
+
- [sendmsg and application execution](#sendmsg-and-application-execution)
|
|
22
|
+
- [event-lock](#event-lock)
|
|
23
|
+
- [Two fibers per connection](#two-fibers-per-connection)
|
|
24
|
+
- [API Documentation](#api-documentation)
|
|
25
|
+
- [License](#license)
|
|
11
26
|
|
|
12
27
|
## Prerequisites
|
|
13
28
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
[wiki page on mod_event_socket](http://wiki.freeswitch.org/wiki/Event_Socket) is
|
|
18
|
-
a good place to start.
|
|
29
|
+
You should be familiar with [mod_event_socket](https://developer.signalwire.com/freeswitch/FreeSWITCH-Explained/Modules/mod_event_socket_1048924/) and the differences between inbound and outbound event sockets before getting started.
|
|
30
|
+
|
|
31
|
+
Requires Ruby 3.0+.
|
|
19
32
|
|
|
20
|
-
|
|
33
|
+
## Installation
|
|
21
34
|
|
|
22
|
-
|
|
35
|
+
Add to your Gemfile:
|
|
23
36
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
37
|
+
```ruby
|
|
38
|
+
gem "librevox"
|
|
39
|
+
```
|
|
27
40
|
|
|
28
|
-
|
|
41
|
+
## Inbound Listener
|
|
29
42
|
|
|
30
|
-
|
|
31
|
-
an event with the specified name arrives.
|
|
43
|
+
Subclass `Librevox::Listener::Inbound` to create an inbound listener. It connects to FreeSWITCH and subscribes to events.
|
|
32
44
|
|
|
33
|
-
|
|
45
|
+
### Events
|
|
34
46
|
|
|
35
|
-
|
|
36
|
-
techniques:
|
|
47
|
+
React to events in two ways:
|
|
37
48
|
|
|
38
|
-
|
|
49
|
+
1. Override `on_event`, called for every event.
|
|
50
|
+
2. Use `event` hooks for specific event names.
|
|
39
51
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
# You can add a hook for a certain event:
|
|
46
|
-
event :channel_hangup do
|
|
47
|
-
# It is instance_eval'ed, so you can use your instance methods etc:
|
|
48
|
-
do_something
|
|
49
|
-
end
|
|
52
|
+
```ruby
|
|
53
|
+
class MyInbound < Librevox::Listener::Inbound
|
|
54
|
+
def on_event(e)
|
|
55
|
+
puts "Got event: #{e.content[:event_name]}"
|
|
56
|
+
end
|
|
50
57
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
...
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def do_something
|
|
58
|
-
...
|
|
59
|
-
end
|
|
60
|
-
end
|
|
58
|
+
event :channel_hangup do
|
|
59
|
+
do_something
|
|
60
|
+
end
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
# The hook block receives a Response when it takes an argument:
|
|
63
|
+
event :channel_bridge do |e|
|
|
64
|
+
puts e.content[:caller_caller_id_number]
|
|
65
|
+
end
|
|
63
66
|
|
|
64
|
-
|
|
67
|
+
def do_something
|
|
68
|
+
# ...
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
```
|
|
65
72
|
|
|
66
|
-
###
|
|
73
|
+
### Event Filtering
|
|
74
|
+
|
|
75
|
+
By default, inbound listeners subscribe to all events. Use `events` to limit which events are received, and `filters` to filter by header values:
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
class MyInbound < Librevox::Listener::Inbound
|
|
79
|
+
events ['CHANNEL_EXECUTE', 'CUSTOM foo']
|
|
80
|
+
filters 'Caller-Context' => ['default', 'example'],
|
|
81
|
+
'Caller-Privacy-Hide-Name' => 'no'
|
|
82
|
+
end
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Outbound Listener
|
|
67
86
|
|
|
68
|
-
|
|
69
|
-
|
|
87
|
+
Subclass `Librevox::Listener::Outbound` to create an outbound listener. FreeSWITCH connects to it when a call hits a socket application in the dialplan.
|
|
88
|
+
|
|
89
|
+
Outbound listeners have the same event functionality as inbound, but scoped to the session.
|
|
70
90
|
|
|
71
91
|
### Dialplan
|
|
72
92
|
|
|
73
|
-
When
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
To
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
93
|
+
When FreeSWITCH connects, `session_initiated` is called. Build your dialplan here.
|
|
94
|
+
|
|
95
|
+
Each application call blocks until FreeSWITCH signals completion (`CHANNEL_EXECUTE_COMPLETE`), so applications execute sequentially:
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
class MyOutbound < Librevox::Listener::Outbound
|
|
99
|
+
def session_initiated
|
|
100
|
+
answer
|
|
101
|
+
digit = play_and_get_digits "enter-digit.wav", "bad-digit.wav"
|
|
102
|
+
bridge "sofia/gateway/trunk/#{digit}"
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Applications that read input (like `play_and_get_digits` and `read`) return the collected value directly.
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
def session_initiated
|
|
111
|
+
answer
|
|
112
|
+
set "foo", "bar"
|
|
113
|
+
multiset "baz" => "1", "qux" => "2"
|
|
114
|
+
playback "welcome.wav"
|
|
115
|
+
hangup
|
|
116
|
+
end
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
For apps not yet wrapped by a named helper, call `application` directly:
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
application "park"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Channel variables are available through `session` (a hash) and `variable`:
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
def session_initiated
|
|
129
|
+
answer
|
|
130
|
+
number = variable(:destination_number)
|
|
131
|
+
playback "greeting-#{number}.wav"
|
|
132
|
+
end
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### API Commands
|
|
136
|
+
|
|
137
|
+
To avoid name clashes between applications and commands, commands are accessed through `api`:
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
def session_initiated
|
|
141
|
+
answer
|
|
142
|
+
api.status
|
|
143
|
+
api.originate 'sofia/user/coltrane', extension: "1234"
|
|
144
|
+
end
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Starting Listeners
|
|
148
|
+
|
|
149
|
+
Start a single listener:
|
|
150
|
+
|
|
151
|
+
```ruby
|
|
152
|
+
Librevox.start MyInbound
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
With connection options:
|
|
156
|
+
|
|
157
|
+
```ruby
|
|
158
|
+
Librevox.start MyInbound, host: "1.2.3.4", port: 8021, auth: "secret"
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Start multiple listeners:
|
|
162
|
+
|
|
163
|
+
```ruby
|
|
164
|
+
Librevox.start do
|
|
165
|
+
run MyInbound
|
|
166
|
+
run MyOutbound, port: 8084
|
|
167
|
+
end
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Default ports are 8021 for inbound and 8084 for outbound.
|
|
171
|
+
|
|
172
|
+
## Closing Connections
|
|
173
|
+
|
|
174
|
+
After a session ends (e.g. the caller hangs up), the outbound socket connection to FreeSWITCH remains open for post-session events. Close it manually when done to avoid lingering sessions. Use `done` (alias for `close_connection_after_writing`):
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
class MyOutbound < Librevox::Listener::Outbound
|
|
178
|
+
event :channel_hangup do
|
|
179
|
+
done
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Command Socket
|
|
185
|
+
|
|
186
|
+
`Librevox::CommandSocket` connects to the FreeSWITCH management console for one-off commands:
|
|
187
|
+
|
|
188
|
+
```ruby
|
|
189
|
+
require "librevox/command_socket"
|
|
190
|
+
|
|
191
|
+
socket = Librevox::CommandSocket.new(server: "127.0.0.1", port: 8021, auth: "ClueCon")
|
|
192
|
+
|
|
193
|
+
socket.originate 'sofia/user/coltrane', extension: "1234"
|
|
194
|
+
#=> #<Librevox::Protocol::Response ...>
|
|
195
|
+
|
|
196
|
+
socket.status
|
|
197
|
+
#=> #<Librevox::Protocol::Response ...>
|
|
198
|
+
|
|
199
|
+
socket.close
|
|
200
|
+
```
|
|
151
201
|
|
|
152
202
|
## Configuration
|
|
153
203
|
|
|
154
|
-
|
|
155
|
-
|
|
204
|
+
```ruby
|
|
205
|
+
Librevox.options[:log_file] = "librevox.log" # default: STDOUT
|
|
206
|
+
Librevox.options[:log_level] = Logger::DEBUG # default: Logger::INFO
|
|
207
|
+
```
|
|
156
208
|
|
|
157
|
-
|
|
158
|
-
Librevox.options[:log_level] = Logger::DEBUG
|
|
209
|
+
When started with `Librevox.start`, sending `SIGHUP` to the process reopens the log file, making it compatible with `logrotate(1)`.
|
|
159
210
|
|
|
160
|
-
##
|
|
211
|
+
## Event Socket Protocol
|
|
161
212
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
standard `logrotate(1)`.
|
|
213
|
+
Understanding the outbound event socket protocol is important for working on
|
|
214
|
+
librevox internals.
|
|
165
215
|
|
|
166
|
-
|
|
216
|
+
### Outbound session lifecycle
|
|
167
217
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
218
|
+
When FreeSWITCH hits a `socket` application in the dialplan, it connects to the
|
|
219
|
+
outbound listener. The listener sends three setup commands before any
|
|
220
|
+
application logic runs:
|
|
171
221
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
>> socket = Librevox::CommandSocket.new
|
|
176
|
-
=> #<Librevox::CommandSocket:0xb7a89104 @server=“127.0.0.1”,
|
|
177
|
-
@socket=#<TCPSocket:0xb7a8908c>, @port=“8021”, @auth=“ClueCon”>
|
|
222
|
+
```
|
|
223
|
+
Listener → FS: connect
|
|
224
|
+
FS → Listener: (channel data — becomes @session)
|
|
178
225
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
>> socket.status
|
|
183
|
-
>> > #<Librevox::Response:0x1016acac8 ...>
|
|
226
|
+
Listener → FS: myevents
|
|
227
|
+
FS → Listener: command/reply +OK
|
|
184
228
|
|
|
185
|
-
|
|
229
|
+
Listener → FS: linger
|
|
230
|
+
FS → Listener: command/reply +OK → triggers session_initiated
|
|
231
|
+
```
|
|
186
232
|
|
|
187
|
-
|
|
188
|
-
`yardoc` from the root of the source tree to generate YARD docs. Look under
|
|
189
|
-
the `Librevox::Commands` and `Librevox::Applications` modules.
|
|
233
|
+
### sendmsg and application execution
|
|
190
234
|
|
|
191
|
-
|
|
235
|
+
When an application (e.g. `answer`, `playback`, `bridge`) is executed via
|
|
236
|
+
`sendmsg`, FreeSWITCH always sends the `command/reply +OK` immediately — it is
|
|
237
|
+
an acknowledgement that the sendmsg was received, **not** that the application
|
|
238
|
+
finished. Application completion is signalled by a `CHANNEL_EXECUTE_COMPLETE`
|
|
239
|
+
event:
|
|
192
240
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
241
|
+
```
|
|
242
|
+
Listener → FS: sendmsg
|
|
243
|
+
call-command: execute
|
|
244
|
+
execute-app-name: playback
|
|
245
|
+
execute-app-arg: welcome.wav
|
|
246
|
+
event-lock: true
|
|
197
247
|
|
|
198
|
-
|
|
248
|
+
FS → Listener: command/reply +OK ← immediate ack
|
|
249
|
+
FS → Listener: CHANNEL_EXECUTE event ← app started
|
|
250
|
+
...app is running...
|
|
251
|
+
FS → Listener: CHANNEL_EXECUTE_COMPLETE event ← app finished
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### event-lock
|
|
255
|
+
|
|
256
|
+
The `event-lock: true` header serializes application execution **on the
|
|
257
|
+
channel**. It does not change what is sent back on the socket.
|
|
258
|
+
|
|
259
|
+
Without `event-lock`, if multiple sendmsg commands are pipelined, FreeSWITCH
|
|
260
|
+
may dequeue and start executing the next application before the current one
|
|
261
|
+
finishes. With `event-lock: true`, FreeSWITCH sets an internal flag
|
|
262
|
+
(`CF_EVENT_LOCK`) on the channel that prevents the next queued sendmsg from
|
|
263
|
+
being processed until the current application completes.
|
|
264
|
+
|
|
265
|
+
### Two fibers per connection
|
|
266
|
+
|
|
267
|
+
Librevox runs two fibers for each outbound connection:
|
|
199
268
|
|
|
200
|
-
(
|
|
201
|
-
|
|
269
|
+
- **Session fiber** (`run_session`) — runs the setup sequence and then
|
|
270
|
+
`session_initiated`. Each `command` or `application` call blocks the fiber
|
|
271
|
+
until the reply arrives.
|
|
272
|
+
- **Read fiber** (`read_loop`) — reads messages from the socket and dispatches
|
|
273
|
+
them to `Async::Queue` instances, waking the session fiber.
|
|
202
274
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
275
|
+
An `Async::Semaphore(1)` mutex on `command` ensures only one command is
|
|
276
|
+
in-flight at a time, so replies are always delivered to the correct caller.
|
|
277
|
+
This also serializes commands issued by event hooks (which run in their own
|
|
278
|
+
fibers) with the main session flow.
|
|
279
|
+
|
|
280
|
+
## API Documentation
|
|
281
|
+
|
|
282
|
+
Applications and commands are documented with YARD. Generate docs with:
|
|
283
|
+
|
|
284
|
+
```
|
|
285
|
+
yard doc
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
See `Librevox::Applications` and `Librevox::Commands` for the full API reference.
|
|
289
|
+
|
|
290
|
+
## License
|
|
206
291
|
|
|
207
|
-
|
|
292
|
+
MIT. See `LICENSE` for details.
|