ruby-asterisk 0.2.0 → 1.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.
Files changed (77) hide show
  1. checksums.yaml +5 -5
  2. data/.claude/skills/ruby-asterisk-agi/SKILL.md +317 -0
  3. data/.claude/skills/ruby-asterisk-ami/SKILL.md +187 -0
  4. data/.claude/skills/ruby-asterisk-ari/SKILL.md +174 -0
  5. data/.claude/skills/ruby-asterisk-ari-websocket/SKILL.md +148 -0
  6. data/.github/workflows/ci.yml +79 -0
  7. data/.gitignore +48 -4
  8. data/.rubocop.yml +84 -0
  9. data/CHANGELOG.md +52 -0
  10. data/Gemfile +3 -1
  11. data/README.md +410 -108
  12. data/Rakefile +3 -5
  13. data/lib/ruby-asterisk/agi/protocol.rb +160 -0
  14. data/lib/ruby-asterisk/agi/server.rb +108 -0
  15. data/lib/ruby-asterisk/agi/session.rb +187 -0
  16. data/lib/ruby-asterisk/ami/client.rb +135 -0
  17. data/lib/ruby-asterisk/ami/commands/channel.rb +55 -0
  18. data/lib/ruby-asterisk/ami/commands/conference.rb +37 -0
  19. data/lib/ruby-asterisk/ami/commands/extension.rb +17 -0
  20. data/lib/ruby-asterisk/ami/commands/mailbox.rb +21 -0
  21. data/lib/ruby-asterisk/ami/commands/monitor.rb +33 -0
  22. data/lib/ruby-asterisk/ami/commands/queue.rb +39 -0
  23. data/lib/ruby-asterisk/ami/commands/sip.rb +25 -0
  24. data/lib/ruby-asterisk/ami/commands/system.rb +50 -0
  25. data/lib/ruby-asterisk/ami/event.rb +22 -0
  26. data/lib/ruby-asterisk/ami/event_list_aggregation.rb +82 -0
  27. data/lib/ruby-asterisk/ami/parser.rb +60 -0
  28. data/lib/ruby-asterisk/ami/promise.rb +108 -0
  29. data/lib/ruby-asterisk/ami/reactor.rb +162 -0
  30. data/lib/ruby-asterisk/ari/client.rb +94 -0
  31. data/lib/ruby-asterisk/ari/resources/base.rb +23 -0
  32. data/lib/ruby-asterisk/ari/resources/bridge.rb +22 -0
  33. data/lib/ruby-asterisk/ari/resources/channel.rb +26 -0
  34. data/lib/ruby-asterisk/ari/resources/collection.rb +27 -0
  35. data/lib/ruby-asterisk/ari/resources/endpoint.rb +22 -0
  36. data/lib/ruby-asterisk/ari/resources/playback.rb +18 -0
  37. data/lib/ruby-asterisk/ari/websocket/connection.rb +143 -0
  38. data/lib/ruby-asterisk/ari/websocket/event_handlers.rb +80 -0
  39. data/lib/ruby-asterisk/ari/websocket/heartbeat.rb +65 -0
  40. data/lib/ruby-asterisk/ari/websocket/reconnect.rb +54 -0
  41. data/lib/ruby-asterisk/ari/websocket/socket_adapter.rb +23 -0
  42. data/lib/ruby-asterisk/ari/websocket.rb +155 -0
  43. data/lib/ruby-asterisk/compat.rb +7 -0
  44. data/lib/ruby-asterisk/error.rb +10 -0
  45. data/lib/ruby-asterisk/parsing_constants.rb +71 -69
  46. data/lib/ruby-asterisk/request.rb +33 -14
  47. data/lib/ruby-asterisk/response.rb +43 -16
  48. data/lib/ruby-asterisk/response_builder.rb +18 -0
  49. data/lib/ruby-asterisk/response_parser.rb +10 -9
  50. data/lib/ruby-asterisk/version.rb +4 -2
  51. data/lib/ruby-asterisk.rb +5 -222
  52. data/ruby-asterisk.gemspec +23 -17
  53. data/spec/ruby-asterisk/agi/protocol_spec.rb +239 -0
  54. data/spec/ruby-asterisk/agi/server_spec.rb +199 -0
  55. data/spec/ruby-asterisk/agi/session_spec.rb +293 -0
  56. data/spec/ruby-asterisk/ami/async_client_spec.rb +256 -0
  57. data/spec/ruby-asterisk/ami/multi_event_spec.rb +57 -0
  58. data/spec/ruby-asterisk/ami/parser_spec.rb +190 -0
  59. data/spec/ruby-asterisk/ami/reactor_spec.rb +290 -0
  60. data/spec/ruby-asterisk/ami_client_spec.rb +68 -0
  61. data/spec/ruby-asterisk/ari/client_spec.rb +168 -0
  62. data/spec/ruby-asterisk/ari/resources/bridge_spec.rb +42 -0
  63. data/spec/ruby-asterisk/ari/resources/channel_spec.rb +57 -0
  64. data/spec/ruby-asterisk/ari/resources/collection_spec.rb +66 -0
  65. data/spec/ruby-asterisk/ari/resources/endpoint_spec.rb +30 -0
  66. data/spec/ruby-asterisk/ari/resources/playback_spec.rb +33 -0
  67. data/spec/ruby-asterisk/ari/websocket_integration_spec.rb +86 -0
  68. data/spec/ruby-asterisk/ari/websocket_spec.rb +374 -0
  69. data/spec/ruby-asterisk/immutability_spec.rb +148 -0
  70. data/spec/ruby-asterisk/request_spec.rb +29 -9
  71. data/spec/ruby-asterisk/response_spec.rb +46 -47
  72. data/spec/spec_helper.rb +12 -0
  73. data/spec/support/mock_ami_server.rb +50 -0
  74. data/spec/support/mock_ari_websocket_server.rb +114 -0
  75. metadata +138 -18
  76. data/CHANGELOG +0 -3
  77. data/spec/ruby-asterisk/ruby_asterisk_spec.rb +0 -192
@@ -0,0 +1,148 @@
1
+ ---
2
+ name: ruby-asterisk-ari-websocket
3
+ description: Receive real-time ARI events from Asterisk via WebSocket with ruby-asterisk: StasisStart, ChannelStateChange, auto-reconnect, ping keep-alive, per-event callbacks.
4
+ ---
5
+
6
+ # ruby-asterisk ARI WebSocket Client
7
+
8
+ ## When to use this skill
9
+
10
+ Use this skill when you need to react to Asterisk events in real time — for example: starting call handling logic when a Stasis call arrives (`StasisStart`), updating a dashboard on channel state changes, or forwarding events to another system.
11
+
12
+ ## Core concepts
13
+
14
+ - The WebSocket client runs a **connection thread that owns the socket read loop** (protocol handled by the pure Ruby `websocket-driver` gem — no EventMachine). `connect` returns immediately; event processing happens in that thread.
15
+ - Register handlers with `on(event_type, &block)` before or after connecting — handlers are stored and called whenever a matching event arrives.
16
+ - **Multiple handlers** for the same event type are supported; all are called in registration order.
17
+ - Auto-reconnect is enabled by default. When the connection drops, the client waits `reconnect_delay` seconds and reconnects. Set `auto_reconnect: false` to disable.
18
+ - A ping is sent every `ping_interval` seconds to keep the connection alive.
19
+
20
+ ## Initialize
21
+
22
+ ```ruby
23
+ require 'ruby-asterisk'
24
+
25
+ ws = RubyAsterisk::ARI::WebSocket.new(
26
+ 'http://192.168.1.1:8088', # ARI base URL
27
+ 'my_api_key', # API key (HTTP Basic auth username)
28
+ 'my_stasis_app', # Stasis application name
29
+ auto_reconnect: true, # default: true
30
+ reconnect_delay: 5, # seconds between reconnect attempts; default: 5
31
+ ping_interval: 30, # keep-alive ping interval in seconds; default: 30
32
+ logger: Logger.new($stdout)
33
+ )
34
+ ```
35
+
36
+ ## API reference
37
+
38
+ | Method | Signature | Notes |
39
+ |---|---|---|
40
+ | `on` | `(event_type, &block)` | Register a callback; `event_type` is a String or Symbol |
41
+ | `connect` | `(&block)` | Start the connection; optional block called on connect |
42
+ | `connected?` | — | `true` if socket is open and in OPEN state |
43
+ | `send_message?` | `(message)` | Send Hash (auto-JSON) or String; returns `false` if not connected |
44
+ | `disconnect` | — | Close the socket and disable auto-reconnect |
45
+
46
+ Constants (readable as class constants):
47
+
48
+ | Constant | Default |
49
+ |---|---|
50
+ | `PING_INTERVAL` | 30 s |
51
+ | `RECONNECT_DELAY` | 5 s |
52
+ | `MAX_RECONNECT_ATTEMPTS` | `nil` (infinite) |
53
+
54
+ ## Common ARI event types
55
+
56
+ | Event type | When fired |
57
+ |---|---|
58
+ | `StasisStart` | A channel enters your Stasis application |
59
+ | `StasisEnd` | A channel leaves your Stasis application |
60
+ | `ChannelStateChange` | Channel state changes (ringing, up, etc.) |
61
+ | `ChannelDtmfReceived` | DTMF digit received on a channel |
62
+ | `ChannelHangupRequest` | Hangup requested on a channel |
63
+ | `BridgeCreated` / `BridgeDestroyed` | Bridge lifecycle |
64
+ | `ChannelEnteredBridge` / `ChannelLeftBridge` | Channel bridge membership |
65
+ | `PlaybackStarted` / `PlaybackFinished` | Media playback lifecycle |
66
+
67
+ Event payloads are plain Ruby Hashes (parsed from JSON). The outer `type` key matches the event type string.
68
+
69
+ ## Usage patterns
70
+
71
+ ### React to incoming Stasis calls
72
+
73
+ ```ruby
74
+ ws.on('StasisStart') do |event|
75
+ channel_id = event['channel']['id']
76
+ caller_id = event.dig('channel', 'caller', 'number')
77
+ puts "Incoming call from #{caller_id} on #{channel_id}"
78
+
79
+ # Use ARI HTTP client to control the channel
80
+ http = RubyAsterisk::ARI::Client.new('http://192.168.1.1:8088', 'key', 'my_stasis_app')
81
+ http.channels.get(channel_id).answer
82
+ end
83
+
84
+ ws.connect
85
+ sleep # keep main thread alive
86
+ ```
87
+
88
+ ### Handle disconnect and cleanup
89
+
90
+ ```ruby
91
+ ws.on('StasisEnd') do |event|
92
+ puts "Call ended: #{event['channel']['id']}"
93
+ end
94
+
95
+ ws.connect do
96
+ puts "Connected to ARI WebSocket"
97
+ end
98
+
99
+ # Graceful shutdown
100
+ at_exit { ws.disconnect }
101
+ ```
102
+
103
+ ### Conditional forwarding
104
+
105
+ ```ruby
106
+ ws.on('ChannelStateChange') do |event|
107
+ next unless event.dig('channel', 'state') == 'Up'
108
+
109
+ channel_id = event['channel']['id']
110
+ puts "Channel #{channel_id} is now answered"
111
+ end
112
+ ```
113
+
114
+ ### Combine with ARI HTTP client
115
+
116
+ ```ruby
117
+ http = RubyAsterisk::ARI::Client.new('http://192.168.1.1:8088', 'key', 'app')
118
+
119
+ ws = RubyAsterisk::ARI::WebSocket.new('http://192.168.1.1:8088', 'key', 'app')
120
+ ws.on('StasisStart') do |event|
121
+ channel = http.channels.get(event['channel']['id'])
122
+ channel.answer
123
+ channel.play('sound:hello-world')
124
+ end
125
+
126
+ ws.connect
127
+ sleep
128
+ ```
129
+
130
+ ## Gotchas
131
+
132
+ - **`connect` is non-blocking** — it starts a background connection thread and returns `self`. The main thread must stay alive (e.g. `sleep`) for events to be processed.
133
+ - **Callbacks execute in the connection thread** — avoid long-running or blocking operations in callbacks; offload to a separate thread if needed. A blocked callback also blocks the read loop.
134
+ - **`send_message?` is thread-safe** — outgoing writes are serialized through an internal reentrant Monitor, so it can be called from any thread (including from inside a callback).
135
+ - **`send_message?` returns `false`** when not connected — always check the return value if delivery matters.
136
+ - **Auto-reconnect** is enabled by default. Call `disconnect` when you actually want to stop; just closing the socket will trigger a reconnect.
137
+ - **`app_name` must match** the Stasis application in Asterisk; events from other applications are not forwarded.
138
+ - Calling `on` after `connect` is safe — handlers are stored in a plain Hash and checked on each incoming message.
139
+
140
+ ## Source files
141
+
142
+ - `lib/ruby-asterisk/ari/websocket.rb` — `WebSocket` class
143
+ - `lib/ruby-asterisk/ari/websocket/connection.rb` — `Connection` module (connect, read loop, reconnect)
144
+ - `lib/ruby-asterisk/ari/websocket/heartbeat.rb` — `Heartbeat` module (ping timer, interruptible waits)
145
+ - `lib/ruby-asterisk/ari/websocket/event_handlers.rb` — `EventHandlers` module (dispatch callbacks)
146
+ - `lib/ruby-asterisk/ari/websocket/socket_adapter.rb` — socket wrapper handed to `websocket-driver`
147
+ - `spec/ruby-asterisk/ari/websocket_spec.rb` — unit specs
148
+ - `spec/ruby-asterisk/ari/websocket_integration_spec.rb` — integration specs against `spec/support/mock_ari_websocket_server.rb`
@@ -0,0 +1,79 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ # Required for the release job below: without a tags filter a tag push does
8
+ # not trigger this workflow at all, so `if: startsWith(github.ref,
9
+ # 'refs/tags/v')` would never be evaluated and no release could ever run.
10
+ tags:
11
+ - 'v*'
12
+ pull_request:
13
+
14
+ jobs:
15
+ lint:
16
+ runs-on: ubuntu-latest
17
+ name: Linting
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ - name: Set up Ruby
21
+ uses: ruby/setup-ruby@v1
22
+ with:
23
+ ruby-version: '3.4'
24
+ bundler-cache: true
25
+ - name: Install dependencies
26
+ run: bundle install
27
+ - name: Run Rubocop
28
+ run: bundle exec rubocop
29
+
30
+ test:
31
+ needs: lint
32
+ runs-on: ubuntu-latest
33
+ strategy:
34
+ fail-fast: false
35
+ matrix:
36
+ ruby:
37
+ - '3.1'
38
+ - '3.2'
39
+ - '3.3'
40
+ - '3.4'
41
+ - '4.0.1'
42
+ - head
43
+ continue-on-error: ${{ endsWith(matrix.ruby, 'head') }}
44
+ name: Ruby ${{ matrix.ruby }}
45
+ steps:
46
+ - uses: actions/checkout@v4
47
+ - name: Set up Ruby
48
+ uses: ruby/setup-ruby@v1
49
+ with:
50
+ ruby-version: ${{ matrix.ruby }}
51
+ bundler-cache: true
52
+ - name: Install dependencies
53
+ run: bundle install
54
+ - name: Run tests
55
+ run: bundle exec rake spec
56
+
57
+ release:
58
+ needs: [lint, test]
59
+ if: startsWith(github.ref, 'refs/tags/v')
60
+ runs-on: ubuntu-latest
61
+ steps:
62
+ - uses: actions/checkout@v4
63
+ - name: Set up Ruby
64
+ uses: ruby/setup-ruby@v1
65
+ with:
66
+ ruby-version: '3.4'
67
+ - name: Install dependencies
68
+ run: bundle install
69
+ - name: Build gem
70
+ run: gem build *.gemspec
71
+ - name: Publish to RubyGems
72
+ run: |
73
+ mkdir -p $HOME/.gem
74
+ touch $HOME/.gem/credentials
75
+ chmod 0600 $HOME/.gem/credentials
76
+ printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
77
+ gem push *.gem
78
+ env:
79
+ GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
data/.gitignore CHANGED
@@ -1,7 +1,51 @@
1
+ # Ignore bundler config.
2
+ /.bundle
3
+
4
+ # Ignore all logfiles and tempfiles.
5
+ /log/*
6
+ /tmp/*
7
+ /docs/*
8
+ !/log/.keep
9
+ !/tmp/.keep
10
+
11
+ # Ignore pidfiles, but keep the directory.
12
+ /tmp/pids/*
13
+ !/tmp/pids/
14
+ !/tmp/pids/.keep
15
+
16
+ # Ignore storage files.
17
+ /storage/*
18
+ !/storage/
19
+ !/storage/.keep
20
+
21
+ # Ignore master key for credentials.
22
+ /config/master.key
23
+
24
+ # Ignore locally-built DNS reverse lookup file.
25
+ /tmp/resolv.conf.pred
26
+
27
+ # Ignore local gems.
28
+ /vendor/bundle/
29
+ /vendor/gems/
30
+ /vendor/cache/
1
31
  *.gem
2
- .bundle
3
- Gemfile.lock
4
- pkg/*
5
- coverage/*
32
+ /.gem/
33
+
34
+ # Ignore documentation files.
35
+ /doc/
36
+ /rdoc/
37
+
38
+ # Ignore compiled files.
39
+ *.o
40
+ *.so
41
+
42
+ # Ignore project-specific files
43
+ .idea/
44
+ .DS_Store
45
+ coverage/
6
46
  .cane
7
47
  *_high_water_mark
48
+ CLAUDE.md
49
+ GEMINI.md
50
+ pkg/
51
+ Gemfile.lock
data/.rubocop.yml ADDED
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ plugins:
4
+ - rubocop-performance
5
+
6
+ AllCops:
7
+ TargetRubyVersion: 3.1
8
+ NewCops: enable
9
+ SuggestExtensions: false
10
+
11
+ # Crucial for Ractor compatibility - all files must have frozen string literal comment
12
+ Style/FrozenStringLiteralComment:
13
+ Enabled: true
14
+ EnforcedStyle: always
15
+
16
+ # Enforce single quotes unless interpolation is needed
17
+ Style/StringLiterals:
18
+ Enabled: true
19
+ EnforcedStyle: single_quotes
20
+
21
+ # Set reasonable limits but exclude large classes that group related commands
22
+ Metrics/ClassLength:
23
+ Enabled: true
24
+ Max: 100
25
+ Exclude:
26
+ - 'lib/ruby-asterisk/ami/client.rb'
27
+ - 'lib/ruby-asterisk/agi/session.rb'
28
+
29
+ # Reasonable method length
30
+ Metrics/MethodLength:
31
+ Enabled: true
32
+ Max: 15
33
+ Exclude:
34
+ - 'spec/**/*'
35
+
36
+ # Allow longer blocks in specs
37
+ Metrics/BlockLength:
38
+ Exclude:
39
+ - 'spec/**/*'
40
+ - '*.gemspec'
41
+
42
+ # Allow longer lines for readability in some cases
43
+ Layout/LineLength:
44
+ Max: 120
45
+ Exclude:
46
+ - 'spec/**/*'
47
+
48
+ # Gemspec file uses string literals from git commands
49
+ Gemspec/DevelopmentDependencies:
50
+ Enabled: false
51
+
52
+ # We explicitly use add_runtime_dependency for clarity about dependency types
53
+ Gemspec/AddRuntimeDependency:
54
+ Enabled: false
55
+
56
+ # Exclude legacy AMI class from parameter list checks until refactored
57
+ Metrics/ParameterLists:
58
+ Exclude:
59
+ - 'lib/ruby-asterisk.rb'
60
+ CountKeywordArgs: false
61
+
62
+ # Exclude legacy AMI class from optional boolean parameter checks
63
+ Style/OptionalBooleanParameter:
64
+ Exclude:
65
+ - 'lib/ruby-asterisk.rb'
66
+
67
+ # The gem filename follows rubygems convention (gem-name.rb)
68
+ Naming/FileName:
69
+ Exclude:
70
+ - 'lib/ruby-asterisk.rb'
71
+
72
+ # Exclude response parser complexity until refactored
73
+ Metrics/AbcSize:
74
+ Exclude:
75
+ - 'lib/ruby-asterisk/response_parser.rb'
76
+
77
+ # Keep `success` method name for backwards compatibility (not success?)
78
+ Naming/PredicateMethod:
79
+ Exclude:
80
+ - 'lib/ruby-asterisk/response.rb'
81
+
82
+ Lint/SuppressedException:
83
+ Exclude:
84
+ - 'spec/support/mock_ami_server.rb'
data/CHANGELOG.md ADDED
@@ -0,0 +1,52 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [1.0.0] - 2026-07-25
11
+
12
+ First release of the rewritten gem: three interfaces (AMI, ARI, AGI) on Ruby ≥ 3.1.
13
+
14
+ ### Added
15
+
16
+ - **AGI::Protocol** — stateless module for AGI wire protocol: `format_command`, `escape_argument`, `quote`, `parse_response`, `error?` (#55)
17
+ - **AGI::Protocol env helpers** — `parse_env_line`, `parse_env_block`, `collect_multiline_error` for robust Asterisk environment parsing and 520 multi-line error handling (#56)
18
+ - **AGI::Session** — full FastAGI session wrapper with fiber-friendly socket I/O: `answer`, `hangup`, `stream_file`, `say_digits`, `say_number`, `exec`, `set_variable`, `get_variable`, `get_data`, `verbose`, `dial`, `wait_for_digit`, `record_file`, `send_text`, `send_image`, `channel_status`, `database_get/put/del/deltree` (#55)
19
+ - **AGI::Server** — FastAGI TCP server backed by Async + Fiber scheduler; each connection runs in its own Fiber sharing a single OS thread (#54)
20
+ - **ARI::WebSocket** — WebSocket client for ARI real-time events: per-event callbacks via `on(type, &block)`, auto-reconnect, configurable ping keep-alive. Built on the pure Ruby `websocket-driver` gem over a plain `TCPSocket` (`SSLSocket` for https) — no EventMachine, so it installs and runs on every supported Ruby including 4.x: a connection thread owns connect/read-loop/reconnect, a ping thread sends keep-alives, and driver access is serialized through a reentrant Monitor so `send_message?` is thread-safe (#53, #82)
21
+ - **ARI::Client** — HTTP client for the Asterisk REST Interface backed by Faraday (#51)
22
+ - **ARI::Resources** — Channel, Bridge, Playback, Endpoint, Collection, and Base resource classes for the ARI HTTP client (#52)
23
+ - **AMI::Client async rewrite** — non-blocking AMI client with `Promise`-based responses: every command returns a `Promise`, call `.value(timeout)` to materialise the `Response`. The connection lives in a `Reactor` made of two plain OS threads (reader + writer) around the stateless `Parser` module, which gives deterministic shutdown on all Ruby ≥ 3.1 (#48, #49, #50, #79)
24
+ - **AMI command modules** — Channel, Conference, Extension, Mailbox, Monitor, Queue, Sip, System extracted into dedicated modules and mixed into `AMI::Client` (#47)
25
+ - `logoff` method on `AMI::Client`
26
+
27
+ ### Changed
28
+
29
+ - **BREAKING:** `RubyAsterisk::AMI::Client` replaces the legacy flat `RubyAsterisk::AMI` class; every command now returns `RubyAsterisk::AMI::Promise` instead of a synchronous `Response`.
30
+ - **BREAKING:** `Rami::VERSION` renamed to `RubyAsterisk::VERSION`; callers reading `Rami::VERSION` will get a `NameError`.
31
+ - Minimum Ruby version set to 3.1.
32
+ - Immutable domain objects (`Request`, `Response`) are now deep-frozen (#45).
33
+ - Namespace migration: all classes moved under `RubyAsterisk::` (#46).
34
+
35
+ ### Fixed
36
+
37
+ - Ruby 3.1 compatibility: `IO#timeout` / `IO#timeout=` shim in `lib/ruby-asterisk/compat.rb`; the `async` gem's Fiber scheduler calls `io.timeout` in Ruby 3.2+ — returning `nil` on 3.1 disables that code path without breaking async I/O (#55).
38
+ - Deterministic teardown on `AMI::Client#disconnect`: both reactor threads are joined and every pending `Promise` is rejected instead of being left dangling (#50, #79).
39
+ - A caller-supplied `action_id:` (`status`, `extension_state`) is now used as the request's ActionID instead of being appended as a second `ActionID` header, which left the response uncorrelated with its `Promise`.
40
+ - `wait_event(timeout: -1)` no longer imposes a 5 s deadline on the returned `Promise`: a negative Timeout means Asterisk waits indefinitely, so `#value` now does too.
41
+
42
+ ### Removed
43
+
44
+ - `Net::Telnet` dependency (replaced by a raw `TCPSocket` owned by the AMI `Reactor`).
45
+
46
+ ---
47
+
48
+ ## [0.1.0]
49
+
50
+ Legacy AMI client based on `Net::Telnet`. Supported: login, originate, hangup, queue operations, ConfBridge, extension state, SIP peers, device state, monitor/record, parked calls, skinny devices.
51
+
52
+ > Earlier history: see `git log v0.1.0` or the [full commit list on GitHub](https://github.com/emilianodellacasa/ruby-asterisk/commits/master).
data/Gemfile CHANGED
@@ -1,4 +1,6 @@
1
- source "http://rubygems.org"
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in rami.gemspec
4
6
  gemspec