librevox 0.8 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 14a0e655fcf9650313c68ffcc4a0e2863a32135b
4
- data.tar.gz: 5605f7a4d07e93d47f1620ce1e2f50a3a87dc755
2
+ SHA256:
3
+ metadata.gz: 6d47382ef56ccc15ea2aacf0e4bc5f4bf197fdb7338a25e7841a7f85a96ebe31
4
+ data.tar.gz: 86736dac97c19aaa298693bfe99c3f75a9b829777f37ea20d2932a1f5221dc3b
5
5
  SHA512:
6
- metadata.gz: 82bc2349155291668c68e85e13aa40c02ff063cc2e60c2c6ad3dfd37480e188253620117ac6301f1a112667c3fb6912a9f7d320363419b673d198f43dac6ba52
7
- data.tar.gz: 4a7a0c9eeca5e5e5c9805518f71711a602dbe0dd15959011542ebb9895c49a87ab1f5ee586762bddd63f68c19d248dede85eb91067051bd00b207d0b912f8ec7
6
+ metadata.gz: efea41f41b6e92608d8dbe69035cbc537c7c1da77c15767c92aa1679633d57c840f26ac37a740f4df3a9ef24a24103bad9bdf2a1ed514bc8d73fd33e0a9576f9
7
+ data.tar.gz: 6a1502164b5df5387b6f1a12c65d379f3fb13caec7a27201c4aa97d1122a22ac667021991802f8158b53e3bd91d2b8b4a0f401709ebee9973331b960bfecff67
data/README.md CHANGED
@@ -1,193 +1,292 @@
1
1
  # Librevox
2
2
 
3
- > An EventMachine-based Ruby library for interacting with the open source
4
- > telephony platform [FreeSWITCH](http://www.freeswitch.org).
5
-
6
- Librevox eventually came to life during a major rewrite of
7
- [Freeswitcher](http://code.rubyists.com/projects/fs/). Not everything would
8
- fit into the existing architecture, and I felt that a blank slate was needed.
9
- Librevox and Freeswitcher looks much alike on the outside, but Librevox tries
10
- to take a simpler approach on the inside.
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
- Librevox lets you interact with FreeSWITCH through mod_event_socket. You
15
- should know how the event socket works, and the differences between inbound and
16
- outbound event sockets before proceeding. The
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
- Librevox is Ruby 1.9-only.
33
+ ## Installation
21
34
 
22
- ## Inbound listener
35
+ Add to your Gemfile:
23
36
 
24
- To create an inbound listener, you should subclass `Librevox::Listener::Inbound`
25
- and add custom behaviour to it. An inbound listener subscribes to all events
26
- from FreeSWITCH, and lets you react on events in two different ways:
37
+ ```ruby
38
+ gem "librevox"
39
+ ```
27
40
 
28
- 1. By overiding `on_event` which gets called every time an event arrives.
41
+ ## Inbound Listener
29
42
 
30
- 2. By adding an event hook with `event`, which will get called every time
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
- The header and content of the event is accessible through `event`.
45
+ ### Events
34
46
 
35
- Below is an example of an inbound listener utilising all the aforementioned
36
- techniques:
47
+ React to events in two ways:
37
48
 
38
- require 'librevox'
49
+ 1. Override `on_event`, called for every event.
50
+ 2. Use `event` hooks for specific event names.
39
51
 
40
- class MyInbound < Librevox::Listener::Inbound
41
- def on_event e
42
- puts "Got event: #{e.content[:event_name]}"
43
- end
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
- # If your hook block takes an argument, a Librevox::Response object for
52
- # the given event is passed on:
53
- event :channel_bridge do |e|
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
- ## Outbound listener
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
- You create an outbound listener by subclassing `Librevox::Listener::Outbound`.
67
+ def do_something
68
+ # ...
69
+ end
70
+ end
71
+ ```
65
72
 
66
- ### Events
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
86
+
87
+ Subclass `Librevox::Listener::Outbound` to create an outbound listener. FreeSWITCH connects to it when a call hits a socket application in the dialplan.
67
88
 
68
- An outbound listener has the same event functionality as the inbound listener,
69
- but it only receives events related to that given session.
89
+ Outbound listeners have the same event functionality as inbound, but scoped to the session.
70
90
 
71
91
  ### Dialplan
72
92
 
73
- When a call is made and Freeswitch connects to the outbound event listener,
74
- `session_initiated` is called. This is where you set up your dialplan:
75
-
76
- def session_initiated
77
- answer do
78
- set "some_var", "some value" do
79
- playback "path/to/file" do
80
- hangup
81
- end
82
- end
83
- end
84
- end
85
-
86
- All channel variables are available as a hash named `session`.
87
-
88
- When using applications that expect a reply, such as `play_and_get_digits`,
89
- you have to use callbacks to read the value, as the function itself returns
90
- immediately due to the async nature of EventMachine:
91
-
92
- def session_initiated
93
- answer do
94
- play_and_get_digits "enter-number.wav", "error.wav" do |digit|
95
- puts "User pressed #{digit}"
96
- playback "thanks-for-the-input.wav" do
97
- hangup
98
- end
99
- end
100
- end
101
- end
102
-
103
- You can also use the commands defined in `Librevox::Command`, which, to avoid
104
- namespace clashes, are accessed through the `api` object:
105
-
106
- def session_initiated
107
- answer do
108
- api.status
109
- end
110
- end
111
-
112
- They can be used in conjunction with applications, and do also take a block,
113
- passing the response to an eventual block argument.
114
-
115
- ## Starting listeners
116
-
117
- To start a single listener, connection/listening on localhost on the default
118
- port is quite simple:
119
-
120
- Librevox.start SomeListener
121
-
122
- it takes an optional hash with arguments:
123
-
124
- Librevox.start SomeListener, :host => "1.2.3.4", :port => "8087", :auth => "pwd"
125
-
126
- Multiple listeners can be started at once by passing a block to `Librevox.start`:
127
-
128
- Librevox.start do
129
- run SomeListener
130
- run OtherListener, :port => "8080"
131
- end
132
-
133
- ## Closing connection
134
-
135
- After a session has finished, e.g. because the calling part hangs up, an
136
- outbound socket still has its connection to FreeSWITCH open, so we can get post-
137
- session events. Therefore it is important that you close the connection manually
138
- when you are done. Otherwise you will have 'hanging' sessions, cloggering up
139
- your system. This can safely be done with `close_connection_after_writing`,
140
- which will wait for all outgoing data to be send before closing the connection.
141
- It is aliased as `done` for convenience.
142
-
143
- Unless you are doing something specific, closing the connection on CHANNEL_HANGUP
144
- is most likely sufficient:
145
-
146
- class MyListener < Librevox::Listener::Outbound
147
- event :channel_hangup do
148
- done
149
- end
150
- end
151
-
152
- ## Using `Librevox::CommandSocket`
153
-
154
- Librevox also ships with a CommandSocket class, which allows you to connect
155
- to the FreeSWITCH management console, from which you can originate calls,
156
- restart FreeSWITCH etc.
157
-
158
- >> require `librevox/command_socket`
159
- => true
160
-
161
- >> socket = Librevox::CommandSocket.new
162
- => #<Librevox::CommandSocket:0xb7a89104 @server=“127.0.0.1”,
163
- @socket=#<TCPSocket:0xb7a8908c>, @port=“8021”, @auth=“ClueCon”>
164
-
165
- >> socket.originate('sofia/user/coltrane', :extension => "1234")
166
- >> #<Librevox::Response:0x10179d388 @content="+OK de0ecbbe-e847...">
167
-
168
- >> socket.status
169
- >> > #<Librevox::Response:0x1016acac8 ...>
170
-
171
- ## Further documentation
172
-
173
- All applications and commands are documented in the code. You can run
174
- `yardoc` from the root of the source tree to generate YARD docs. Look under
175
- the `Librevox::Commands` and `Librevox::Applications` modules.
176
-
177
- ## Extras
178
-
179
- * Source: [http://github.com/vangberg/librevox](http://github.com/vangberg/librevox)
180
- * API docs: [http://rdoc.info/projects/vangberg/librevox](http://rdoc.info/projects/vangberg/librevox)
181
- * Mailing list: librevox@librelist.com
182
- * IRC: #librevox @ irc.freenode.net
93
+ When FreeSWITCH connects, `session_initiated` is called. Build your dialplan here.
183
94
 
184
- ## License
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
185
 
186
- (c) 2009-2014 Harry Vangberg <harry@vangberg.name>
187
- (c) 2011-2014 Firmafon ApS <info@firmafon.dk>
186
+ `Librevox::CommandSocket` connects to the FreeSWITCH management console for one-off commands:
188
187
 
189
- Librevox was inspired by and uses code from Freeswitcher, which is distributed
190
- under the MIT license and (c) 2009 The Rubyists (Jayson Vaughn, Tj Vanderpoel,
191
- Michael Fellinger, Kevin Berry), Harry Vangberg, see `LICENSE-Freeswitcher`.
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
+ ```
201
+
202
+ ## Configuration
203
+
204
+ ```ruby
205
+ Librevox.options[:log_file] = "librevox.log" # default: STDOUT
206
+ Librevox.options[:log_level] = Logger::DEBUG # default: Logger::INFO
207
+ ```
208
+
209
+ When started with `Librevox.start`, sending `SIGHUP` to the process reopens the log file, making it compatible with `logrotate(1)`.
210
+
211
+ ## Event Socket Protocol
212
+
213
+ Understanding the outbound event socket protocol is important for working on
214
+ librevox internals.
215
+
216
+ ### Outbound session lifecycle
217
+
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:
221
+
222
+ ```
223
+ Listener → FS: connect
224
+ FS → Listener: (channel data — becomes @session)
225
+
226
+ Listener → FS: myevents
227
+ FS → Listener: command/reply +OK
228
+
229
+ Listener → FS: linger
230
+ FS → Listener: command/reply +OK → triggers session_initiated
231
+ ```
232
+
233
+ ### sendmsg and application execution
234
+
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:
240
+
241
+ ```
242
+ Listener → FS: sendmsg
243
+ call-command: execute
244
+ execute-app-name: playback
245
+ execute-app-arg: welcome.wav
246
+ event-lock: true
247
+
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:
268
+
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.
274
+
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
192
291
 
193
- Librevox is distributed under the terms of the MIT license, see `LICENSE`.
292
+ MIT. See `LICENSE` for details.