freeswitch-esl-client 0.1.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 +7 -0
- data/CHANGELOG.md +40 -0
- data/LICENSE +21 -0
- data/README.md +305 -0
- data/freeswitch-esl-client.gemspec +40 -0
- data/lib/freeswitch-esl/client.rb +382 -0
- data/lib/freeswitch-esl/connection.rb +35 -0
- data/lib/freeswitch-esl/errors.rb +7 -0
- data/lib/freeswitch-esl/outbound/connection.rb +155 -0
- data/lib/freeswitch-esl/outbound/server.rb +65 -0
- data/lib/freeswitch-esl/outbound/session.rb +218 -0
- data/lib/freeswitch-esl/parser.rb +106 -0
- data/lib/freeswitch-esl/response.rb +61 -0
- data/lib/freeswitch-esl/version.rb +3 -0
- data/lib/freeswitch-esl-client.rb +4 -0
- data/lib/freeswitch-esl.rb +25 -0
- metadata +128 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a50fbec915b234efd22756ad6b06bfbebf8df7598e5a5db71f66a3af37713bf0
|
|
4
|
+
data.tar.gz: cf7f08880ca0e01f42278f03c24ee6f55bbd45fbe4de8749e09ac60a4ffc7eb6
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: c07ab1867f846fa12bdf11f8fe7319ab520711735cb91cc98bab480836a98559f927f2bd8680dec6c000c41074f2304649c8f4e001f1904c38f29b9dba9f2d7f
|
|
7
|
+
data.tar.gz: 70bff8ac50d2ce9d3486dd4262c727c82c35e32c5deacfc261f5ceada8999fd12400d988cf56ef5fb29edc03f853bd0c6c4084447ca9438f40c3ec03a6eea869
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-10-29
|
|
10
|
+
|
|
11
|
+
First public release. Verified live against FreeSWITCH 1.11 and Asterisk 20 in Docker,
|
|
12
|
+
on Ruby 3.2+ and 4.0.
|
|
13
|
+
|
|
14
|
+
The gem is published as `freeswitch-esl-client` because `freeswitch-esl` was already
|
|
15
|
+
taken on RubyGems. The library name is unchanged: `require 'freeswitch-esl'` and the
|
|
16
|
+
`FreeswitchESL` namespace. `require 'freeswitch-esl-client'` is available as a shim.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **Inbound mode** — `FreeswitchESL::Client` connects to `mod_event_socket` (default
|
|
21
|
+
`127.0.0.1:8021`), authenticates, subscribes to events, auto-reconnects, and exposes
|
|
22
|
+
`api` / `bgapi` with callbacks.
|
|
23
|
+
- **Outbound mode** — `FreeswitchESL.outbound_server` starts a TCP server that
|
|
24
|
+
FreeSWITCH dials into (`socket host:port async full`). Every call becomes a
|
|
25
|
+
`FreeswitchESL::Outbound::Session` with `answer`, `play`, `bridge`, `transfer`,
|
|
26
|
+
`set`, `export`, `speak`, `record`, `read`, `send_dtmf`, `execute`, `api`, `bgapi`.
|
|
27
|
+
- Output-side `connect` handshake and automatic event subscription for outbound
|
|
28
|
+
sockets, with per-call event filtering by `Unique-ID`.
|
|
29
|
+
- `Client#originate` with `application:`, `exten:`/`dialplan:`/`context:`,
|
|
30
|
+
positional `cid_name`/`cid_num`/`timeout`, and the three FreeSWITCH variable
|
|
31
|
+
scopes: `ultra_vars:` (`<>`), `vars:` (`{}`), `leg_vars:` (`[]`).
|
|
32
|
+
- Binary-safe `Framer` that honours `Content-Length` and never splits frames on
|
|
33
|
+
blank lines, plus a parser for nested BACKGROUND_JOB bodies.
|
|
34
|
+
- `Response` helpers: `success?`, `error?`, `reply_text`, `uuid`, `job_uuid`,
|
|
35
|
+
`event_name`, `body` / `output`.
|
|
36
|
+
- Call helpers on the inbound client: `answer_call`, `kill`/`hangup`,
|
|
37
|
+
`bridge_calls`, `play_file`, `stop_playback`, `set_var`, `get_var`, `transfer`,
|
|
38
|
+
`park`, `status`, `channels`.
|
|
39
|
+
- RSpec suite (26 examples) with a fake ESL server covering inbound, outbound and
|
|
40
|
+
originate command building.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Michael
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
# freeswitch-esl-client
|
|
2
|
+
|
|
3
|
+
[](https://rubygems.org/gems/freeswitch-esl-client)
|
|
4
|
+
|
|
5
|
+
A small, dependency-light Ruby client for the [FreeSWITCH](https://freeswitch.org/)
|
|
6
|
+
Event Socket Layer (`mod_event_socket`), with **inbound** and **outbound** modes.
|
|
7
|
+
|
|
8
|
+
> The gem is published as **`freeswitch-esl-client`** because `freeswitch-esl` was
|
|
9
|
+
> already taken on RubyGems. The library keeps its original name: `require 'freeswitch-esl'`
|
|
10
|
+
> (as does the module, `FreeswitchESL`). `require 'freeswitch-esl-client'` works too.
|
|
11
|
+
|
|
12
|
+
- **Inbound** — your Ruby process connects to FreeSWITCH on `:8021`, subscribes to
|
|
13
|
+
events and drives calls with `api` / `bgapi`.
|
|
14
|
+
- **Outbound** — FreeSWITCH dials into an embedded TCP server of yours
|
|
15
|
+
(`<action application="socket" data="host:port async full"/>`), so call handling
|
|
16
|
+
lives in Ruby instead of static dialplan XML. Ideal for building a B2BUA, IVR or
|
|
17
|
+
dynamic routing service.
|
|
18
|
+
|
|
19
|
+
Verified live against FreeSWITCH 1.11 and Asterisk 20 in Docker, on Ruby 3.2+ and 4.0.
|
|
20
|
+
|
|
21
|
+
## Contents
|
|
22
|
+
|
|
23
|
+
- [Requirements](#requirements)
|
|
24
|
+
- [Installation](#installation)
|
|
25
|
+
- [Inbound mode](#inbound-mode)
|
|
26
|
+
- [Originating calls](#originating-calls)
|
|
27
|
+
- [Outbound mode](#outbound-mode)
|
|
28
|
+
- [Threading and callbacks](#threading-and-callbacks)
|
|
29
|
+
- [Gotchas](#gotchas)
|
|
30
|
+
- [Development](#development)
|
|
31
|
+
- [License](#license)
|
|
32
|
+
|
|
33
|
+
## Requirements
|
|
34
|
+
|
|
35
|
+
- Ruby >= 3.2
|
|
36
|
+
- FreeSWITCH with `mod_event_socket` loaded
|
|
37
|
+
(`conf/autoload_configs/event_socket.conf.xml`, default password `ClueCon`)
|
|
38
|
+
- Nothing else at runtime except [`eventmachine`](https://rubygems.org/gems/eventmachine)
|
|
39
|
+
|
|
40
|
+
## Installation
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
# Gemfile
|
|
44
|
+
gem 'freeswitch-esl-client'
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or straight from Git while a release is not published yet:
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
gem 'freeswitch-esl-client', git: 'https://github.com/miclast/freeswitch-esl-client.git', tag: 'v0.1.0'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Or from a local checkout:
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
gem 'freeswitch-esl-client', path: '../freeswitch-esl'
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Then:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
bundle install
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Inbound mode
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
require 'freeswitch-esl'
|
|
69
|
+
require 'eventmachine'
|
|
70
|
+
|
|
71
|
+
EM.run do
|
|
72
|
+
client = FreeswitchESL::Client.new(
|
|
73
|
+
host: '127.0.0.1',
|
|
74
|
+
port: 8021,
|
|
75
|
+
password: 'ClueCon'
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
client.on('CHANNEL_ANSWER') { |event| puts "answered #{event['Unique-ID']}" }
|
|
79
|
+
client.on('CHANNEL_HANGUP_COMPLETE') { |event| puts "hung up: #{event['Hangup-Cause']}" }
|
|
80
|
+
client.on('*') { |event| puts event['Event-Name'] } # catch-all
|
|
81
|
+
|
|
82
|
+
client.connect # inside a running reactor
|
|
83
|
+
# client.connect! # without a reactor: spawns its own thread
|
|
84
|
+
|
|
85
|
+
EM.add_timer(1) do
|
|
86
|
+
client.api('status') { |res| puts res.body }
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Constructor options
|
|
92
|
+
|
|
93
|
+
| option | default | description |
|
|
94
|
+
| --- | --- | --- |
|
|
95
|
+
| `host` | `'127.0.0.1'` | ESL host |
|
|
96
|
+
| `port` | `8021` | ESL port |
|
|
97
|
+
| `password` | `'ClueCon'` | ESL password |
|
|
98
|
+
| `logger` | `Logger.new($stdout)` | any `Logger`-compatible object |
|
|
99
|
+
| `reconnect_interval` | `2` | seconds between reconnect attempts |
|
|
100
|
+
| `events` | `Client::DEFAULT_EVENTS` | events to subscribe to |
|
|
101
|
+
| `auto_reconnect` | `true` | reconnect automatically after a dropped connection |
|
|
102
|
+
|
|
103
|
+
Commands issued before authentication are queued and flushed once the socket is
|
|
104
|
+
ready; `client.ready?` tells whether that has happened.
|
|
105
|
+
|
|
106
|
+
### Commands
|
|
107
|
+
|
|
108
|
+
`api(command) { |res| … }` runs synchronously on the FreeSWITCH side and returns an
|
|
109
|
+
`api/response`; `bgapi(command) { |res| … }` returns immediately and resolves via a
|
|
110
|
+
`BACKGROUND_JOB` event. The gem maps the server-generated `Job-UUID` back to your
|
|
111
|
+
block, so callbacks stay in order even with concurrent jobs.
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
client.bgapi('status') { |res| puts res.reply_text } # +OK Job-UUID: …
|
|
115
|
+
client.api('show channels count') { |res| puts res.body } # "2 total."
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Call helpers
|
|
119
|
+
|
|
120
|
+
Convenience wrappers over the API (all callback-based):
|
|
121
|
+
|
|
122
|
+
| helper | FreeSWITCH command |
|
|
123
|
+
| --- | --- |
|
|
124
|
+
| `answer_call(uuid)` | `uuid_answer` |
|
|
125
|
+
| `kill(uuid, cause = 'NORMAL_CLEARING')` / `hangup` | `uuid_kill` |
|
|
126
|
+
| `bridge_calls(uuid1, uuid2)` | `uuid_bridge` |
|
|
127
|
+
| `play_file(uuid, file, leg = 'aleg')` | `uuid_broadcast` |
|
|
128
|
+
| `stop_playback(uuid)` | `uuid_break … all` |
|
|
129
|
+
| `set_var(uuid, name, value)` | `uuid_setvar` |
|
|
130
|
+
| `get_var(uuid, name)` | `uuid_getvar` |
|
|
131
|
+
| `transfer(uuid, destination, dialplan = 'XML', context = 'default')` | `uuid_transfer` |
|
|
132
|
+
| `park(uuid)` | `uuid_park` |
|
|
133
|
+
| `status` | `status` |
|
|
134
|
+
| `channels` | `show channels as json` |
|
|
135
|
+
|
|
136
|
+
### Response object
|
|
137
|
+
|
|
138
|
+
| method | meaning |
|
|
139
|
+
| --- | --- |
|
|
140
|
+
| `success?` / `error?` | `+OK …` / `-ERR …` (works for bgapi too, where the text is in the body) |
|
|
141
|
+
| `reply_text` | `Reply-Text` header or the first body line |
|
|
142
|
+
| `uuid` | uuid from `+OK <uuid>` (handy for originate) |
|
|
143
|
+
| `job_uuid`, `event_name` | job/event identifiers |
|
|
144
|
+
| `body` / `output` | raw payload (API output, BACKGROUND_JOB output) |
|
|
145
|
+
| `[key]` | any header of the reply/event |
|
|
146
|
+
|
|
147
|
+
## Originating calls
|
|
148
|
+
|
|
149
|
+
`Client#originate` builds the FreeSWITCH `originate` command for you:
|
|
150
|
+
|
|
151
|
+
```ruby
|
|
152
|
+
client.originate('sofia/gateway/agent/555',
|
|
153
|
+
application: '&park()', # or exten: '1000'
|
|
154
|
+
vars: {
|
|
155
|
+
origination_caller_id_number: '1001',
|
|
156
|
+
origination_caller_id_name: 'Agent',
|
|
157
|
+
ignore_early_media: 'false'
|
|
158
|
+
},
|
|
159
|
+
leg_vars: { effective_caller_id_name: 'Agent' }) do |res|
|
|
160
|
+
if res.success?
|
|
161
|
+
uuid = res.uuid # +OK <uuid>
|
|
162
|
+
else
|
|
163
|
+
warn res.reply_text # -ERR INVALID_GATEWAY
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
A positional form maps to FreeSWITCH's `[dialplan] [context] [cid_name] [cid_num] [timeout_sec]`:
|
|
169
|
+
|
|
170
|
+
```ruby
|
|
171
|
+
client.originate('user/1000', cid_name: 'Support', cid_num: '1001', timeout: 30)
|
|
172
|
+
# => bgapi originate user/1000 &park() XML default Support 1001 30
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Missing intermediate slots are sent as the literal `undef`, which FreeSWITCH turns
|
|
176
|
+
into `NULL`. Empty strings would be collapsed by its argv parser and shift the
|
|
177
|
+
remaining arguments (the timeout would silently become the caller ID).
|
|
178
|
+
|
|
179
|
+
### Variable scopes
|
|
180
|
+
|
|
181
|
+
FreeSWITCH accepts three prefixes on a dial string and they are not equivalent:
|
|
182
|
+
|
|
183
|
+
| scope | option | applies to |
|
|
184
|
+
| --- | --- | --- |
|
|
185
|
+
| `<...>` | `ultra_vars` | every leg of the originate |
|
|
186
|
+
| `{...}` | `vars` | the originated channel, plus originate control variables (`originate_timeout`, `ignore_early_media`, `origination_caller_id_*`, retries, `group_confirm_*`) |
|
|
187
|
+
| `[...]` | `leg_vars` | per-destination session variables |
|
|
188
|
+
|
|
189
|
+
They are emitted in that order: `<ultra>{global}[leg]dialstring`. Note that when the
|
|
190
|
+
same name appears in `{}` and `[]`, FreeSWITCH 1.11 keeps the `{}` value — use
|
|
191
|
+
distinct names per scope instead of relying on an override.
|
|
192
|
+
|
|
193
|
+
## Outbound mode
|
|
194
|
+
|
|
195
|
+
FreeSWITCH makes the call, then hands the channel to your Ruby process:
|
|
196
|
+
|
|
197
|
+
```xml
|
|
198
|
+
<!-- dialplan -->
|
|
199
|
+
<extension name="app">
|
|
200
|
+
<condition field="destination_number" expression="^9999$">
|
|
201
|
+
<action application="socket" data="10.0.0.5:8084 async full"/>
|
|
202
|
+
</condition>
|
|
203
|
+
</extension>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
require 'freeswitch-esl'
|
|
208
|
+
require 'eventmachine'
|
|
209
|
+
|
|
210
|
+
EM.run do
|
|
211
|
+
server = FreeswitchESL.outbound_server(host: '0.0.0.0', port: 8084) do |session|
|
|
212
|
+
puts "call from #{session.caller_id_number} to #{session.destination_number}"
|
|
213
|
+
|
|
214
|
+
session.on('DTMF') { |e| puts "digit #{e['DTMF-Digit']}" }
|
|
215
|
+
session.on('CHANNEL_HANGUP_COMPLETE') { |e| puts "cause #{e['Hangup-Cause']}" }
|
|
216
|
+
|
|
217
|
+
session.answer
|
|
218
|
+
session.play('ivr/ivr-welcome_to_freeswitch.wav')
|
|
219
|
+
session.bridge('sofia/gateway/trunk/555')
|
|
220
|
+
session.hangup('NORMAL_CLEARING')
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
server.start
|
|
224
|
+
# server.stop when shutting the application down
|
|
225
|
+
end
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### How it works
|
|
229
|
+
|
|
230
|
+
1. FreeSWITCH connects and waits for the `connect` command — the server sends it
|
|
231
|
+
automatically in `post_init`.
|
|
232
|
+
2. FreeSWITCH answers with a `command/reply` frame that also carries the channel
|
|
233
|
+
data (`Unique-ID`, caller, destination, …); that becomes the `Session`.
|
|
234
|
+
3. The channel is parked. The session subscribes to its events and is driven with
|
|
235
|
+
`sendmsg` (`execute`, `answer`, `bridge`, …).
|
|
236
|
+
4. Events are filtered by the session UUID (`Unique-ID`, `Bridge-A/B-Unique-ID`,
|
|
237
|
+
`Other-Leg-Unique-ID`, `Originating-Leg-Unique-ID`), so concurrent calls on the
|
|
238
|
+
same socket stay isolated.
|
|
239
|
+
|
|
240
|
+
### Session API
|
|
241
|
+
|
|
242
|
+
| group | methods |
|
|
243
|
+
| --- | --- |
|
|
244
|
+
| state | `uuid`, `caller_id_number`, `caller_id_name`, `destination_number`, `state`, `hangup_cause`, `answered?`, `ended?` |
|
|
245
|
+
| call control | `answer`, `pre_answer`, `ring_ready`, `hangup(cause)`, `bridge(dialstring)`, `transfer(extension, dialplan:, context:)`, `park` |
|
|
246
|
+
| media | `play(file)`, `play_and_get_digits(...)`, `read(...)`, `record(file, limit, ...)`, `speak(text, engine:, voice:)`, `sleep(ms)` |
|
|
247
|
+
| variables | `set(name, value)`, `export(name, value)`, `unset(name)`, `variable(name)`, `[](name)`, `headers` |
|
|
248
|
+
| signals | `send_dtmf(dtmf)`, `flush_dtmf` |
|
|
249
|
+
| escape hatch | `execute(app, args)`, `api(command)`, `bgapi(command)`, `send_command(raw)` |
|
|
250
|
+
| events | `on('DTMF') { … }`, `on('*') { … }` |
|
|
251
|
+
|
|
252
|
+
`Session::DEFAULT_EVENTS` can be tuned in the constant if you need fewer events per call.
|
|
253
|
+
|
|
254
|
+
## Threading and callbacks
|
|
255
|
+
|
|
256
|
+
EventMachine runs everything on a single reactor thread. Event handlers and
|
|
257
|
+
`api`/`bgapi` callbacks are invoked there, so keep them short: push work into a queue
|
|
258
|
+
(Sidekiq, a Ruby `Queue`, etc.) instead of doing blocking I/O inside a callback.
|
|
259
|
+
|
|
260
|
+
Calling `connect!` outside a reactor starts one owned by the client; `close!` stops
|
|
261
|
+
it. Inside an existing reactor use `connect` and manage the reactor yourself.
|
|
262
|
+
|
|
263
|
+
## Gotchas
|
|
264
|
+
|
|
265
|
+
- **Outbound sockets receive no events until they subscribe.** `listener_run` in
|
|
266
|
+
`mod_event_socket.c` only delivers events after `event plain …`; the gem does this
|
|
267
|
+
for you in `start_session`.
|
|
268
|
+
- **`bgapi` ignores a client-supplied `job-uuid=`.** FreeSWITCH generates its own and
|
|
269
|
+
reports it in the reply; the gem parses and matches it.
|
|
270
|
+
- **Codecs must be in the bridge dial string** when a dialplan `set` is not honoured
|
|
271
|
+
on the outbound leg:
|
|
272
|
+
`session.bridge('{absolute_codec_string=PCMU,PCMA}sofia/gateway/trunk/555')`.
|
|
273
|
+
- **`Caller-Caller-ID-*` may be rewritten** by a gateway/profile; check `Caller-ANI`
|
|
274
|
+
and `variable_origination_caller_id_*` when testing caller ID.
|
|
275
|
+
- **Frames are content-length aware.** The parser never splits on blank lines, because
|
|
276
|
+
`BACKGROUND_JOB` bodies contain their own header block.
|
|
277
|
+
|
|
278
|
+
## Development
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
bundle install
|
|
282
|
+
bundle exec rspec # 26 examples, no external services required
|
|
283
|
+
rake build # builds pkg/freeswitch-esl-client-0.1.0.gem
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
The spec suite spins up fake ESL servers in-process, so FreeSWITCH does not need to
|
|
287
|
+
be running for the tests.
|
|
288
|
+
|
|
289
|
+
### Releasing
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
rake release # tags, pushes and uploads to rubygems.org
|
|
293
|
+
# or manually:
|
|
294
|
+
gem build freeswitch-esl-client.gemspec
|
|
295
|
+
gem push freeswitch-esl-client-0.1.0.gem
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## Contributing
|
|
299
|
+
|
|
300
|
+
Issues and pull requests are welcome. Please run `bundle exec rspec` before opening a
|
|
301
|
+
PR and keep the public API backwards compatible within a minor version.
|
|
302
|
+
|
|
303
|
+
## License
|
|
304
|
+
|
|
305
|
+
Released under the [MIT License](LICENSE).
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require_relative 'lib/freeswitch-esl/version'
|
|
2
|
+
|
|
3
|
+
Gem::Specification.new do |spec|
|
|
4
|
+
spec.name = 'freeswitch-esl-client'
|
|
5
|
+
spec.version = FreeswitchESL::VERSION
|
|
6
|
+
spec.authors = ['Michael']
|
|
7
|
+
spec.email = ['michael@example.com']
|
|
8
|
+
|
|
9
|
+
spec.summary = 'Ruby client for the FreeSWITCH Event Socket Layer (inbound and outbound).'
|
|
10
|
+
spec.description = <<~DESC
|
|
11
|
+
EventMachine-based client for the FreeSWITCH Event Socket Layer (mod_event_socket).
|
|
12
|
+
Supports inbound mode (connect to port 8021, subscribe to events, run api/bgapi,
|
|
13
|
+
auto-reconnect) and outbound mode (an embedded TCP server that FreeSWITCH dials into
|
|
14
|
+
with the socket application, so call control lives in Ruby instead of dialplan XML).
|
|
15
|
+
Includes a binary-safe ESL frame parser, an originate helper covering FreeSWITCH
|
|
16
|
+
variable scopes and positional arguments, and per-channel outbound sessions.
|
|
17
|
+
DESC
|
|
18
|
+
spec.homepage = 'https://github.com/miclast/freeswitch-esl-client'
|
|
19
|
+
spec.license = 'MIT'
|
|
20
|
+
spec.required_ruby_version = '>= 3.2'
|
|
21
|
+
|
|
22
|
+
spec.metadata = {
|
|
23
|
+
'homepage_uri' => spec.homepage,
|
|
24
|
+
'changelog_uri' => "#{spec.homepage}/blob/main/CHANGELOG.md",
|
|
25
|
+
'bug_tracker_uri' => "#{spec.homepage}/issues",
|
|
26
|
+
'documentation_uri' => 'https://www.rubydoc.info/gems/freeswitch-esl-client',
|
|
27
|
+
'rubygems_mfa_required' => 'true'
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
spec.files = Dir.glob('{lib,exe}/**/*') + %w[README.md CHANGELOG.md LICENSE freeswitch-esl-client.gemspec]
|
|
31
|
+
spec.bindir = 'exe'
|
|
32
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
33
|
+
spec.require_paths = ['lib']
|
|
34
|
+
|
|
35
|
+
spec.add_dependency 'eventmachine', '~> 1.2', '>= 1.2.7'
|
|
36
|
+
spec.add_dependency 'logger', '>= 1.6'
|
|
37
|
+
|
|
38
|
+
spec.add_development_dependency 'rake', '~> 13.0'
|
|
39
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
|
40
|
+
end
|