didww-v3 6.0.0 → 6.1.1
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 +4 -4
- data/CHANGELOG.md +20 -0
- data/README.md +40 -0
- data/didww-v3.gemspec +13 -8
- data/examples/README.md +1 -0
- data/examples/voice_in_trunk_sip_registration.rb +83 -0
- data/examples/voice_in_trunks.rb +65 -0
- data/lib/didww/complex_objects/authentication_methods/credentials_and_ip.rb +2 -2
- data/lib/didww/complex_objects/base.rb +35 -2
- data/lib/didww/complex_objects/configurations/sip_configuration.rb +125 -1
- data/lib/didww/version.rb +1 -1
- metadata +32 -24
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d834eeacdcc690b1b19e390a31730342681b1efe94d1e5f124d9b8c82d9ba5ca
|
|
4
|
+
data.tar.gz: 694e8a57d08a8cd646d715d424a1824aff726b3af40190db4749417de158c3ca
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0565f13367337588e62fadcc5464a3aaea268567b8b7af51e1c0f1b221027be039f242d1fae74e3a1b8980edff2add2cd394055b7e4c30a4bc453c0e39e15288
|
|
7
|
+
data.tar.gz: 44ceb7b1488be5a77b1d1b15a3ed45fd963a09e280941a5a14a7476fa331355ac81768974149aab51d44a38fe1d0f2717fb8fd38fd456704e6e9ccbe505d9879
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,26 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
5
5
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [6.1.1] - 2026-05-04
|
|
8
|
+
### Fixed
|
|
9
|
+
- `SipConfiguration#enabled_sip_registration = true` now always emits `host: null` and `port: null` on the wire, not only when the local attribute hash already had them set. The previous conditional cascade (introduced in 6.1.0) silently no-op'd when re-enabling SIP registration on an existing trunk through a fresh `SipConfiguration` instance: the PATCH body carried only `enabled_sip_registration: true`, the server merged it with the trunk's persisted host, and rejected with 422 (`host must be blank when the SIP registration is enabled`). Verified end-to-end against the live sandbox via the new `examples/voice_in_trunk_sip_registration.rb`.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `examples/voice_in_trunk_sip_registration.rb` — end-to-end create / rename / disable / re-enable flow demonstrating every cascade rule against the sandbox API.
|
|
13
|
+
|
|
14
|
+
## [6.1.0] - 2026-05-04
|
|
15
|
+
### Added
|
|
16
|
+
- Complete the 2026-04-16 `SipConfiguration` attribute set that was missed during the 2026-04-16 rollout (and is not present in the public Postman collection — server form is the source of truth):
|
|
17
|
+
- `enabled_sip_registration` (bool) — enables SIP registration; the API generates `incoming_auth_*` credentials when set true and requires `port` to be blank.
|
|
18
|
+
- `use_did_in_ruri` (bool) — uses DID number in R-URI; requires SIP registration to be enabled.
|
|
19
|
+
- `network_protocol_priority` (string enum) — `force_ipv4` / `force_ipv6` / `any` / `prefer_ipv4` / `prefer_ipv6`. Constants exposed as `NETWORK_PROTOCOL_PRIORITY_*` and `NETWORK_PROTOCOL_PRIORITIES`.
|
|
20
|
+
- `diversion_inject_mode` (string enum) — `none` / `did_number`. Constants exposed as `DIVERSION_INJECT_MODE_*` and `DIVERSION_INJECT_MODES`.
|
|
21
|
+
- `cnam_lookup` (bool) — enables CNAM resolution for inbound calls.
|
|
22
|
+
- `SipConfiguration#incoming_auth_username` and `#incoming_auth_password` — read-only server-generated SIP authentication credentials returned on `voice_in_trunks` when `enabled_sip_registration` is true.
|
|
23
|
+
- `ComplexObject::Base.property` accepts `read_only: true`. Read-only attributes are still parsed from server responses but are stripped from the JSON:API write payload, so round-tripping a loaded configuration through PATCH no longer triggers `400 Param not allowed`.
|
|
24
|
+
- `ComplexObject::Base.property` accepts `sensitive: true`. The default `#inspect` representation redacts those values with `[FILTERED]` so credentials never leak through default logging / REPL / unhandled exception output. Marked as sensitive: `SipConfiguration#auth_password`, `SipConfiguration#incoming_auth_username/password`, and `AuthenticationMethod::CredentialsAndIp#username/password`.
|
|
25
|
+
- `SipConfiguration` auto-cascades dependent fields whose constraints are server-enforced when set via property setters: `enabled_sip_registration = true` clears `host`/`port` (when previously set), `enabled_sip_registration = false` forces `use_did_in_ruri = false`, and assigning a non-blank `host` flips both flags. Constructor / `[]=` assignments bypass the cascade so server responses deserialize as-is.
|
|
26
|
+
|
|
7
27
|
## [6.0.0] - 2026-04-24
|
|
8
28
|
Support for DIDWW API version **2026-04-16**. The gem now sends `X-DIDWW-API-Version: 2026-04-16` with every request by default. Users staying on API `2022-05-10` should pin to the [`2022-05-10`](https://github.com/didww/didww-v3-ruby/tree/2022-05-10) branch (5.x).
|
|
9
29
|
|
data/README.md
CHANGED
|
@@ -205,6 +205,46 @@ trunk = DIDWW::Client.voice_in_trunks.new(
|
|
|
205
205
|
trunk.save
|
|
206
206
|
```
|
|
207
207
|
|
|
208
|
+
#### SIP Registration (2026-04-16)
|
|
209
|
+
|
|
210
|
+
A Voice In Trunk can also be configured to authenticate inbound calls
|
|
211
|
+
via SIP registration credentials generated by DIDWW. The SDK
|
|
212
|
+
auto-cascades the dependent fields the server requires:
|
|
213
|
+
|
|
214
|
+
* setting `enabled_sip_registration = true` clears any previously-set
|
|
215
|
+
`host` / `port` (the server rejects them with 422 otherwise);
|
|
216
|
+
* setting `host` to a non-blank value flips `enabled_sip_registration`
|
|
217
|
+
back to `false` and forces `use_did_in_ruri = false` so the server
|
|
218
|
+
accepts the disable PATCH.
|
|
219
|
+
|
|
220
|
+
DIDWW generates `incoming_auth_username` and `incoming_auth_password`
|
|
221
|
+
and surfaces them in the response when SIP registration is enabled.
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
trunk = DIDWW::Client.voice_in_trunks.new(
|
|
225
|
+
name: 'Office (registered)',
|
|
226
|
+
configuration: DIDWW::ComplexObject::SipConfiguration.new.tap do |c|
|
|
227
|
+
c.enabled_sip_registration = true
|
|
228
|
+
c.use_did_in_ruri = true
|
|
229
|
+
c.cnam_lookup = true
|
|
230
|
+
end
|
|
231
|
+
)
|
|
232
|
+
trunk.save
|
|
233
|
+
# trunk.configuration.incoming_auth_username -- server-generated
|
|
234
|
+
# trunk.configuration.incoming_auth_password -- server-generated
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
To disable SIP registration on an existing trunk, just set `host` —
|
|
238
|
+
the cascade flips `enabled_sip_registration` to `false` and
|
|
239
|
+
`use_did_in_ruri` to `false` automatically:
|
|
240
|
+
|
|
241
|
+
```ruby
|
|
242
|
+
trunk.configuration = DIDWW::ComplexObject::SipConfiguration.new.tap do |c|
|
|
243
|
+
c.host = 'sip.example.com'
|
|
244
|
+
end
|
|
245
|
+
trunk.save
|
|
246
|
+
```
|
|
247
|
+
|
|
208
248
|
### Voice Out Trunks
|
|
209
249
|
|
|
210
250
|
Voice Out Trunks use a polymorphic `authentication_method` (2026-04-16). Three types are supported:
|
data/didww-v3.gemspec
CHANGED
|
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
|
11
11
|
spec.email = ['alex.k@didww.com']
|
|
12
12
|
|
|
13
13
|
spec.summary = %q{Ruby client for DIDWW API v3}
|
|
14
|
-
spec.description = %q{Ruby client for DIDWW API v3}
|
|
14
|
+
spec.description = %q{Ruby client for the DIDWW JSON:API v3, covering DID inventory, voice in/out trunks, regulatory documents, exports, and emergency calling services.}
|
|
15
15
|
spec.homepage = 'https://github.com/didww/didww-v3-ruby'
|
|
16
16
|
spec.license = 'MIT'
|
|
17
17
|
|
|
@@ -24,11 +24,16 @@ Gem::Specification.new do |spec|
|
|
|
24
24
|
|
|
25
25
|
spec.required_ruby_version = '>= 3.3'
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
spec.add_dependency '
|
|
33
|
-
spec.add_dependency '
|
|
27
|
+
# Lower bounds match the oldest version verified by CI; upper bounds
|
|
28
|
+
# cap on the next known-incompatible major so consumers get a clear
|
|
29
|
+
# constraint failure rather than a runtime surprise. The CI matrix in
|
|
30
|
+
# .github/workflows/tests.yml exercises activesupport ~> 7.2 / 8.0 /
|
|
31
|
+
# 8.1 — declaring a wider lower bound than that would be aspirational.
|
|
32
|
+
spec.add_dependency 'activesupport', '>= 7.2', '< 9'
|
|
33
|
+
spec.add_dependency 'faraday', '~> 2.0'
|
|
34
|
+
spec.add_dependency 'faraday-multipart', '~> 1.0'
|
|
35
|
+
spec.add_dependency 'json_api_client', '1.23.0'
|
|
36
|
+
spec.add_dependency 'http', '~> 5.0'
|
|
37
|
+
spec.add_dependency 'down', '~> 5.0'
|
|
38
|
+
spec.add_dependency 'openssl-oaep', '~> 0.1'
|
|
34
39
|
end
|
data/examples/README.md
CHANGED
|
@@ -41,6 +41,7 @@ DIDWW_API_KEY=your_api_key ruby examples/orders_nanpa.rb
|
|
|
41
41
|
| Script | Description |
|
|
42
42
|
|---|---|
|
|
43
43
|
| [`voice_in_trunks.rb`](voice_in_trunks.rb) | Lists voice in trunks and their configurations. |
|
|
44
|
+
| [`voice_in_trunk_sip_registration.rb`](voice_in_trunk_sip_registration.rb) | End-to-end SIP registration flow: create with `enabled_sip_registration: true`, rename, disable by setting `host`, re-enable by toggling the flag. The SDK keeps the dependent fields (`host`, `port`, `use_did_in_ruri`) aligned with the server's validation rules automatically. |
|
|
44
45
|
| [`voice_in_trunk_groups.rb`](voice_in_trunk_groups.rb) | CRUD for trunk groups with trunk relationships. |
|
|
45
46
|
|
|
46
47
|
### Voice Out (Outbound)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
#
|
|
3
|
+
# End-to-end SIP registration flow on /voice_in_trunks (API 2026-04-16):
|
|
4
|
+
# create with sip_registration enabled → rename → disable by setting
|
|
5
|
+
# `host` → re-enable by toggling the flag. The SDK keeps the
|
|
6
|
+
# dependent fields (`host`, `port`, `use_did_in_ruri`) aligned with
|
|
7
|
+
# the server's validation rules automatically. The sandbox trunk is
|
|
8
|
+
# left in place after the script completes so it can be inspected
|
|
9
|
+
# afterwards.
|
|
10
|
+
#
|
|
11
|
+
# Usage: DIDWW_API_KEY=your_sandbox_key ruby examples/voice_in_trunk_sip_registration.rb
|
|
12
|
+
|
|
13
|
+
require 'bundler/setup'
|
|
14
|
+
require 'didww'
|
|
15
|
+
|
|
16
|
+
DIDWW::Client.configure do |client|
|
|
17
|
+
client.api_key = ENV.fetch('DIDWW_API_KEY') { abort 'Please set DIDWW_API_KEY' }
|
|
18
|
+
client.api_mode = :sandbox
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
puts "=== Ruby SDK v#{DIDWW::VERSION} — SIP registration flow ==="
|
|
22
|
+
|
|
23
|
+
# 1) Create a SIP trunk with sip_registration enabled. The server
|
|
24
|
+
# generates `incoming_auth_*` credentials and returns them in the
|
|
25
|
+
# response — they are read-only and stripped from any subsequent
|
|
26
|
+
# write payload by the SDK.
|
|
27
|
+
puts "\n[1/4] Create with sip_registration enabled..."
|
|
28
|
+
trunk = DIDWW::Client.voice_in_trunks.new(
|
|
29
|
+
name: "sip-registration-example-#{Time.now.to_i}",
|
|
30
|
+
priority: 1,
|
|
31
|
+
weight: 100,
|
|
32
|
+
cli_format: 'e164',
|
|
33
|
+
ringing_timeout: 30,
|
|
34
|
+
configuration: DIDWW::ComplexObject::SipConfiguration.new.tap do |c|
|
|
35
|
+
c.enabled_sip_registration = true
|
|
36
|
+
c.use_did_in_ruri = true
|
|
37
|
+
c.cnam_lookup = false
|
|
38
|
+
c.codec_ids = [9, 7]
|
|
39
|
+
c.transport_protocol_id = 1
|
|
40
|
+
end
|
|
41
|
+
)
|
|
42
|
+
abort "create failed: #{trunk.errors.full_messages.join('; ')}" unless trunk.save
|
|
43
|
+
puts " id=#{trunk.id}"
|
|
44
|
+
puts " incoming_auth_username=#{trunk.configuration.incoming_auth_username.inspect}"
|
|
45
|
+
puts " incoming_auth_password=#{trunk.configuration.incoming_auth_password.inspect}"
|
|
46
|
+
trunk_id = trunk.id
|
|
47
|
+
|
|
48
|
+
# 2) Rename — a single-field PATCH that doesn't touch SIP-registration state.
|
|
49
|
+
puts "\n[2/4] Rename trunk..."
|
|
50
|
+
trunk.name = "sip-registration-renamed-#{Time.now.to_i}"
|
|
51
|
+
abort "rename failed: #{trunk.errors.full_messages.join('; ')}" unless trunk.save
|
|
52
|
+
puts " name=#{trunk.name}"
|
|
53
|
+
|
|
54
|
+
# 3) Disable sip_registration by setting `host`. The SDK flips
|
|
55
|
+
# `enabled_sip_registration` and `use_did_in_ruri` to false in the
|
|
56
|
+
# same PATCH so the server-side validators accept the change.
|
|
57
|
+
puts "\n[3/4] Disable by setting host..."
|
|
58
|
+
trunk.configuration = DIDWW::ComplexObject::SipConfiguration.new.tap do |c|
|
|
59
|
+
c.host = '203.0.113.10'
|
|
60
|
+
end
|
|
61
|
+
abort "disable failed: #{trunk.errors.full_messages.join('; ')}" unless trunk.save
|
|
62
|
+
fresh = DIDWW::Client.voice_in_trunks.find(trunk_id).first
|
|
63
|
+
puts " enabled_sip_registration=#{fresh.configuration.enabled_sip_registration.inspect}"
|
|
64
|
+
puts " use_did_in_ruri=#{fresh.configuration.use_did_in_ruri.inspect}"
|
|
65
|
+
puts " host=#{fresh.configuration.host.inspect}"
|
|
66
|
+
puts " incoming_auth_username=#{fresh.configuration.incoming_auth_username.inspect}"
|
|
67
|
+
|
|
68
|
+
# 4) Re-enable sip_registration. Setting `enabled_sip_registration = true`
|
|
69
|
+
# sends host=nil / port=nil on the wire so the server (which still has
|
|
70
|
+
# the host from step 3) is told to clear them.
|
|
71
|
+
puts "\n[4/4] Re-enable by toggling enabled_sip_registration..."
|
|
72
|
+
trunk = DIDWW::Client.voice_in_trunks.find(trunk_id).first
|
|
73
|
+
trunk.configuration = DIDWW::ComplexObject::SipConfiguration.new.tap do |c|
|
|
74
|
+
c.enabled_sip_registration = true
|
|
75
|
+
c.use_did_in_ruri = true
|
|
76
|
+
end
|
|
77
|
+
abort "re-enable failed: #{trunk.errors.full_messages.join('; ')}" unless trunk.save
|
|
78
|
+
fresh = DIDWW::Client.voice_in_trunks.find(trunk_id).first
|
|
79
|
+
puts " enabled_sip_registration=#{fresh.configuration.enabled_sip_registration.inspect}"
|
|
80
|
+
puts " host=#{fresh.configuration.host.inspect}"
|
|
81
|
+
puts " incoming_auth_username=#{fresh.configuration.incoming_auth_username.inspect}"
|
|
82
|
+
|
|
83
|
+
puts "\n=== PASS — trunk #{trunk_id} left in sandbox ==="
|
data/examples/voice_in_trunks.rb
CHANGED
|
@@ -41,3 +41,68 @@ if !trunks.empty?
|
|
|
41
41
|
puts " Description: #{specific_trunk.description}"
|
|
42
42
|
puts " Created at: #{specific_trunk.created_at}"
|
|
43
43
|
end
|
|
44
|
+
|
|
45
|
+
# 2026-04-16 — surface the API 2026-04-16 SIP-registration-related attributes when
|
|
46
|
+
# the trunk uses a SIP configuration. `incoming_auth_username` /
|
|
47
|
+
# `incoming_auth_password` are server-generated and only present when
|
|
48
|
+
# `enabled_sip_registration` is true.
|
|
49
|
+
sip_trunk = trunks.find { |t| t.configuration.is_a?(DIDWW::ComplexObject::SipConfiguration) }
|
|
50
|
+
if sip_trunk
|
|
51
|
+
puts "\n=== SIP Configuration (2026-04-16 attributes) ==="
|
|
52
|
+
config = sip_trunk.configuration
|
|
53
|
+
puts "Trunk: #{sip_trunk.name}"
|
|
54
|
+
puts " enabled_sip_registration: #{config.enabled_sip_registration.inspect}"
|
|
55
|
+
puts " use_did_in_ruri: #{config.use_did_in_ruri.inspect}"
|
|
56
|
+
puts " network_protocol_priority: #{config.network_protocol_priority.inspect}"
|
|
57
|
+
puts " diversion_relay_policy: #{config.diversion_relay_policy.inspect}"
|
|
58
|
+
puts " diversion_inject_mode: #{config.diversion_inject_mode.inspect}"
|
|
59
|
+
puts " cnam_lookup: #{config.cnam_lookup.inspect}"
|
|
60
|
+
if config.enabled_sip_registration
|
|
61
|
+
puts " incoming_auth_username: #{config.incoming_auth_username.inspect}"
|
|
62
|
+
puts " incoming_auth_password: #{config.incoming_auth_password.inspect}"
|
|
63
|
+
else
|
|
64
|
+
puts ' (incoming_auth credentials only appear when SIP registration is enabled)'
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Example: create a new SIP trunk with SIP registration enabled. The server
|
|
69
|
+
# generates `incoming_auth_username` / `incoming_auth_password` and returns
|
|
70
|
+
# them in the response. They are READ-ONLY — sending them on POST/PATCH
|
|
71
|
+
# returns 400 Param not allowed; the SDK strips them from write payloads.
|
|
72
|
+
#
|
|
73
|
+
# Server-side rules to keep in mind:
|
|
74
|
+
# * When `enabled_sip_registration: true`, `host` and `port` MUST be left
|
|
75
|
+
# blank (the server will register inbound and pick the host itself).
|
|
76
|
+
# * When DISABLING SIP registration on an existing trunk, you must
|
|
77
|
+
# simultaneously set `host` to a non-blank value and `use_did_in_ruri`
|
|
78
|
+
# to `false` in the same PATCH — otherwise the server returns 422.
|
|
79
|
+
#
|
|
80
|
+
# Uncomment to actually create a trunk in your sandbox account:
|
|
81
|
+
#
|
|
82
|
+
# new_trunk = DIDWW::Resource::VoiceInTrunk.new(
|
|
83
|
+
# name: "SIP-registration trunk #{Time.now.to_i}",
|
|
84
|
+
# priority: 1,
|
|
85
|
+
# weight: 100,
|
|
86
|
+
# cli_format: 'e164',
|
|
87
|
+
# ringing_timeout: 30,
|
|
88
|
+
# configuration: DIDWW::ComplexObject::SipConfiguration.new(
|
|
89
|
+
# # NOTE: do NOT set `host` here — the server requires it to be blank
|
|
90
|
+
# # when enabled_sip_registration is true.
|
|
91
|
+
# enabled_sip_registration: true,
|
|
92
|
+
# use_did_in_ruri: true,
|
|
93
|
+
# cnam_lookup: false,
|
|
94
|
+
# diversion_relay_policy: 'as_is',
|
|
95
|
+
# diversion_inject_mode: 'did_number',
|
|
96
|
+
# network_protocol_priority: 'prefer_ipv4',
|
|
97
|
+
# codec_ids: [9, 7],
|
|
98
|
+
# transport_protocol_id: 1
|
|
99
|
+
# )
|
|
100
|
+
# )
|
|
101
|
+
# created = new_trunk.save
|
|
102
|
+
# if created
|
|
103
|
+
# puts "Created trunk #{new_trunk.id}"
|
|
104
|
+
# puts " incoming_auth_username: #{new_trunk.configuration.incoming_auth_username}"
|
|
105
|
+
# puts " incoming_auth_password: #{new_trunk.configuration.incoming_auth_password}"
|
|
106
|
+
# else
|
|
107
|
+
# puts "Errors: #{new_trunk.errors.full_messages}"
|
|
108
|
+
# end
|
|
@@ -12,8 +12,8 @@ module DIDWW
|
|
|
12
12
|
# only in responses; they cannot be set from the client on create.
|
|
13
13
|
property :allowed_sip_ips, type: :strings
|
|
14
14
|
property :tech_prefix, type: :string
|
|
15
|
-
property :username, type: :string
|
|
16
|
-
property :password, type: :string
|
|
15
|
+
property :username, type: :string, sensitive: true
|
|
16
|
+
property :password, type: :string, sensitive: true
|
|
17
17
|
end
|
|
18
18
|
end
|
|
19
19
|
end
|
|
@@ -13,6 +13,8 @@ module DIDWW
|
|
|
13
13
|
|
|
14
14
|
def property(name, options = {})
|
|
15
15
|
schema.add(name, options)
|
|
16
|
+
read_only_attributes << name.to_s if options[:read_only]
|
|
17
|
+
sensitive_attributes << name.to_s if options[:sensitive]
|
|
16
18
|
define_method(name.to_sym) { self[name] }
|
|
17
19
|
define_method("#{name}=".to_sym) { |val| self[name] = val }
|
|
18
20
|
end
|
|
@@ -21,6 +23,22 @@ module DIDWW
|
|
|
21
23
|
@schema ||= JsonApiClient::Schema.new
|
|
22
24
|
end
|
|
23
25
|
|
|
26
|
+
# Names of attributes that the server returns but does not accept on
|
|
27
|
+
# write. Excluded from `as_json` so that round-tripping a resource
|
|
28
|
+
# through PATCH does not echo them back and trigger 400 Param not
|
|
29
|
+
# allowed.
|
|
30
|
+
def read_only_attributes
|
|
31
|
+
@read_only_attributes ||= []
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Names of attributes whose values are credentials/secrets. The wire
|
|
35
|
+
# format is unchanged — `as_json` still emits the real values — but
|
|
36
|
+
# `#inspect` redacts them so default logging / error reports / REPL
|
|
37
|
+
# echoes never leak credentials downstream.
|
|
38
|
+
def sensitive_attributes
|
|
39
|
+
@sensitive_attributes ||= []
|
|
40
|
+
end
|
|
41
|
+
|
|
24
42
|
# Type casting for JsonApiClient parser/setters
|
|
25
43
|
def cast(value, default)
|
|
26
44
|
case value
|
|
@@ -77,14 +95,29 @@ module DIDWW
|
|
|
77
95
|
end
|
|
78
96
|
|
|
79
97
|
# When we represent this resource for serialization (create/update), we do so
|
|
80
|
-
# with this implementation
|
|
98
|
+
# with this implementation. Read-only attributes are excluded — the server
|
|
99
|
+
# rejects them with 400 Param not allowed.
|
|
81
100
|
def as_json(*)
|
|
101
|
+
excluded = self.class.read_only_attributes
|
|
82
102
|
{ type: type }.with_indifferent_access.tap do |h|
|
|
83
|
-
h[:attributes] = attributes.as_json
|
|
103
|
+
h[:attributes] = attributes.as_json.reject { |k, _| excluded.include?(k.to_s) }
|
|
84
104
|
end
|
|
85
105
|
end
|
|
86
106
|
|
|
87
107
|
def as_json_api(*); as_json end
|
|
108
|
+
|
|
109
|
+
# Redacts sensitive attribute values so credentials never leak into
|
|
110
|
+
# default logs / REPL output / error reports. The on-the-wire payload
|
|
111
|
+
# is unaffected (see `#as_json`).
|
|
112
|
+
FILTERED = '[FILTERED]'.freeze
|
|
113
|
+
def inspect
|
|
114
|
+
sensitive = self.class.sensitive_attributes
|
|
115
|
+
formatted = attributes.map do |k, v|
|
|
116
|
+
display = sensitive.include?(k.to_s) && !v.nil? ? FILTERED : v
|
|
117
|
+
"#{k}=#{display.inspect}"
|
|
118
|
+
end
|
|
119
|
+
"#<#{self.class.name} #{formatted.join(' ')}>"
|
|
120
|
+
end
|
|
88
121
|
end
|
|
89
122
|
end
|
|
90
123
|
end
|
|
@@ -18,7 +18,7 @@ module DIDWW
|
|
|
18
18
|
# Nullable: No
|
|
19
19
|
# Description: Optional authorization user for the SIP server
|
|
20
20
|
|
|
21
|
-
property :auth_password, type: :string
|
|
21
|
+
property :auth_password, type: :string, sensitive: true
|
|
22
22
|
# Type: String
|
|
23
23
|
# Nullable: No
|
|
24
24
|
# Description: Optional authorization password for the SIP server
|
|
@@ -180,6 +180,54 @@ module DIDWW
|
|
|
180
180
|
# See DIVERSION_RELAY_POLICIES for available values.
|
|
181
181
|
# In API v3.4 this attribute was named `diversion_relay_mode`.
|
|
182
182
|
|
|
183
|
+
property :diversion_inject_mode, type: :string
|
|
184
|
+
# Type: String
|
|
185
|
+
# Nullable: No
|
|
186
|
+
# Description: Diversion header injection mode. See
|
|
187
|
+
# DIVERSION_INJECT_MODES for available values. (API 2026-04-16)
|
|
188
|
+
|
|
189
|
+
property :network_protocol_priority, type: :string
|
|
190
|
+
# Type: String
|
|
191
|
+
# Nullable: No
|
|
192
|
+
# Description: SIP network protocol priority. See
|
|
193
|
+
# NETWORK_PROTOCOL_PRIORITIES for available values. (API 2026-04-16)
|
|
194
|
+
|
|
195
|
+
property :enabled_sip_registration, type: :boolean
|
|
196
|
+
# Type: Boolean
|
|
197
|
+
# Nullable: No
|
|
198
|
+
# Description: Enables SIP registration. When true the API generates
|
|
199
|
+
# `incoming_auth_username` / `incoming_auth_password`; the trunk's
|
|
200
|
+
# `host` and `port` must be left blank. When disabling sip
|
|
201
|
+
# registration on an existing trunk, the same PATCH must also set
|
|
202
|
+
# `host` to a non-blank value and `use_did_in_ruri` to false, or
|
|
203
|
+
# the server returns 422. (API 2026-04-16)
|
|
204
|
+
|
|
205
|
+
property :use_did_in_ruri, type: :boolean
|
|
206
|
+
# Type: Boolean
|
|
207
|
+
# Nullable: No
|
|
208
|
+
# Description: When true, the trunk's R-URI uses the DID number.
|
|
209
|
+
# Requires `enabled_sip_registration` to be true. (API 2026-04-16)
|
|
210
|
+
|
|
211
|
+
property :cnam_lookup, type: :boolean
|
|
212
|
+
# Type: Boolean
|
|
213
|
+
# Nullable: No
|
|
214
|
+
# Description: Enables CNAM resolution for inbound calls on this
|
|
215
|
+
# trunk. (API 2026-04-16)
|
|
216
|
+
|
|
217
|
+
property :incoming_auth_username, type: :string, read_only: true, sensitive: true
|
|
218
|
+
# Type: String
|
|
219
|
+
# Nullable: Yes
|
|
220
|
+
# Description: Server-generated SIP authentication username, returned in
|
|
221
|
+
# responses when `enabled_sip_registration` is true. Read-only; the API
|
|
222
|
+
# rejects any write attempt with 400 Param not allowed. (API 2026-04-16)
|
|
223
|
+
|
|
224
|
+
property :incoming_auth_password, type: :string, read_only: true, sensitive: true
|
|
225
|
+
# Type: String
|
|
226
|
+
# Nullable: Yes
|
|
227
|
+
# Description: Server-generated SIP authentication password, returned in
|
|
228
|
+
# responses when `enabled_sip_registration` is true. Read-only; the API
|
|
229
|
+
# rejects any write attempt with 400 Param not allowed. (API 2026-04-16)
|
|
230
|
+
|
|
183
231
|
DIVERSION_RELAY_POLICY_NONE = 'none'
|
|
184
232
|
DIVERSION_RELAY_POLICY_AS_IS = 'as_is'
|
|
185
233
|
DIVERSION_RELAY_POLICY_SIP = 'sip'
|
|
@@ -192,6 +240,28 @@ module DIDWW
|
|
|
192
240
|
DIVERSION_RELAY_POLICY_TEL
|
|
193
241
|
].freeze
|
|
194
242
|
|
|
243
|
+
DIVERSION_INJECT_MODE_NONE = 'none'
|
|
244
|
+
DIVERSION_INJECT_MODE_DID_NUMBER = 'did_number'
|
|
245
|
+
|
|
246
|
+
DIVERSION_INJECT_MODES = [
|
|
247
|
+
DIVERSION_INJECT_MODE_NONE,
|
|
248
|
+
DIVERSION_INJECT_MODE_DID_NUMBER
|
|
249
|
+
].freeze
|
|
250
|
+
|
|
251
|
+
NETWORK_PROTOCOL_PRIORITY_FORCE_IPV4 = 'force_ipv4'
|
|
252
|
+
NETWORK_PROTOCOL_PRIORITY_FORCE_IPV6 = 'force_ipv6'
|
|
253
|
+
NETWORK_PROTOCOL_PRIORITY_ANY = 'any'
|
|
254
|
+
NETWORK_PROTOCOL_PRIORITY_PREFER_IPV4 = 'prefer_ipv4'
|
|
255
|
+
NETWORK_PROTOCOL_PRIORITY_PREFER_IPV6 = 'prefer_ipv6'
|
|
256
|
+
|
|
257
|
+
NETWORK_PROTOCOL_PRIORITIES = [
|
|
258
|
+
NETWORK_PROTOCOL_PRIORITY_FORCE_IPV4,
|
|
259
|
+
NETWORK_PROTOCOL_PRIORITY_FORCE_IPV6,
|
|
260
|
+
NETWORK_PROTOCOL_PRIORITY_ANY,
|
|
261
|
+
NETWORK_PROTOCOL_PRIORITY_PREFER_IPV4,
|
|
262
|
+
NETWORK_PROTOCOL_PRIORITY_PREFER_IPV6
|
|
263
|
+
].freeze
|
|
264
|
+
|
|
195
265
|
MEDIA_ENCRYPTION_MODES = [
|
|
196
266
|
'disabled',
|
|
197
267
|
'srtp_sdes',
|
|
@@ -257,6 +327,60 @@ module DIDWW
|
|
|
257
327
|
def transport_protocol
|
|
258
328
|
TRANSPORT_PROTOCOLS[transport_protocol_id]
|
|
259
329
|
end
|
|
330
|
+
|
|
331
|
+
# Auto-cascade for server-enforced field dependencies (2026-04-16).
|
|
332
|
+
#
|
|
333
|
+
# The server validates these combinations and rejects mismatches with
|
|
334
|
+
# 422; the SDK fixes them up at assignment time so user code never has
|
|
335
|
+
# to track the full server-side rule set. Future server-required
|
|
336
|
+
# cascades are added here without the caller needing to know.
|
|
337
|
+
#
|
|
338
|
+
# Rules:
|
|
339
|
+
# * `enabled_sip_registration = true` => host = nil, port = nil
|
|
340
|
+
# (server requires both blank when registration is enabled)
|
|
341
|
+
# * `enabled_sip_registration = false` => use_did_in_ruri = false
|
|
342
|
+
# (server requires use_did_in_ruri disabled when sip_registration is)
|
|
343
|
+
# * `host = <non-nil>` => enabled_sip_registration = false,
|
|
344
|
+
# use_did_in_ruri = false (host requires sip_registration disabled,
|
|
345
|
+
# which in turn requires use_did_in_ruri disabled)
|
|
346
|
+
#
|
|
347
|
+
# Constructor-time `[]=` assignments bypass these setters intentionally
|
|
348
|
+
# so that responses returned from the server (e.g. a fixture with
|
|
349
|
+
# `enabled_sip_registration: true` AND `host: nil`) deserialize as-is.
|
|
350
|
+
def enabled_sip_registration=(val)
|
|
351
|
+
case val
|
|
352
|
+
when true
|
|
353
|
+
# Always emit host: null and port: null on the wire when
|
|
354
|
+
# enabling sip_registration. The server requires both blank
|
|
355
|
+
# (returns 422 otherwise) AND a PATCH against an existing
|
|
356
|
+
# trunk that already has host/port set on the server side
|
|
357
|
+
# MUST explicitly nullify them — the SDK's local attributes
|
|
358
|
+
# hash starts empty, so there's nothing to "preserve".
|
|
359
|
+
self[:host] = nil
|
|
360
|
+
self[:port] = nil
|
|
361
|
+
when false
|
|
362
|
+
# Server requires use_did_in_ruri = false whenever sip_registration
|
|
363
|
+
# is disabled. Always emit it on the wire so the server's check
|
|
364
|
+
# passes regardless of the prior state of the field.
|
|
365
|
+
self[:use_did_in_ruri] = false
|
|
366
|
+
else
|
|
367
|
+
# `nil` (or any non-bool) — caller is unsetting the field. No
|
|
368
|
+
# cascade: dependent fields stay as the caller left them.
|
|
369
|
+
end
|
|
370
|
+
self[:enabled_sip_registration] = val
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def host=(val)
|
|
374
|
+
unless val.nil?
|
|
375
|
+
# Setting host implies sip_registration is disabled (server-side
|
|
376
|
+
# validation), which in turn implies use_did_in_ruri = false.
|
|
377
|
+
# Always emit both on the wire so the server's checks pass
|
|
378
|
+
# without the caller having to know the rule set.
|
|
379
|
+
self[:enabled_sip_registration] = false
|
|
380
|
+
self[:use_did_in_ruri] = false
|
|
381
|
+
end
|
|
382
|
+
self[:host] = val
|
|
383
|
+
end
|
|
260
384
|
end
|
|
261
385
|
end
|
|
262
386
|
end
|
data/lib/didww/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: didww-v3
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 6.
|
|
4
|
+
version: 6.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alex Korobeinikov
|
|
@@ -15,42 +15,48 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - ">="
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '
|
|
18
|
+
version: '7.2'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '9'
|
|
19
22
|
type: :runtime
|
|
20
23
|
prerelease: false
|
|
21
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
25
|
requirements:
|
|
23
26
|
- - ">="
|
|
24
27
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '
|
|
28
|
+
version: '7.2'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '9'
|
|
26
32
|
- !ruby/object:Gem::Dependency
|
|
27
33
|
name: faraday
|
|
28
34
|
requirement: !ruby/object:Gem::Requirement
|
|
29
35
|
requirements:
|
|
30
|
-
- - "
|
|
36
|
+
- - "~>"
|
|
31
37
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '0'
|
|
38
|
+
version: '2.0'
|
|
33
39
|
type: :runtime
|
|
34
40
|
prerelease: false
|
|
35
41
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
42
|
requirements:
|
|
37
|
-
- - "
|
|
43
|
+
- - "~>"
|
|
38
44
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: '0'
|
|
45
|
+
version: '2.0'
|
|
40
46
|
- !ruby/object:Gem::Dependency
|
|
41
47
|
name: faraday-multipart
|
|
42
48
|
requirement: !ruby/object:Gem::Requirement
|
|
43
49
|
requirements:
|
|
44
|
-
- - "
|
|
50
|
+
- - "~>"
|
|
45
51
|
- !ruby/object:Gem::Version
|
|
46
|
-
version: '0'
|
|
52
|
+
version: '1.0'
|
|
47
53
|
type: :runtime
|
|
48
54
|
prerelease: false
|
|
49
55
|
version_requirements: !ruby/object:Gem::Requirement
|
|
50
56
|
requirements:
|
|
51
|
-
- - "
|
|
57
|
+
- - "~>"
|
|
52
58
|
- !ruby/object:Gem::Version
|
|
53
|
-
version: '0'
|
|
59
|
+
version: '1.0'
|
|
54
60
|
- !ruby/object:Gem::Dependency
|
|
55
61
|
name: json_api_client
|
|
56
62
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -69,45 +75,46 @@ dependencies:
|
|
|
69
75
|
name: http
|
|
70
76
|
requirement: !ruby/object:Gem::Requirement
|
|
71
77
|
requirements:
|
|
72
|
-
- - "
|
|
78
|
+
- - "~>"
|
|
73
79
|
- !ruby/object:Gem::Version
|
|
74
|
-
version: '0'
|
|
80
|
+
version: '5.0'
|
|
75
81
|
type: :runtime
|
|
76
82
|
prerelease: false
|
|
77
83
|
version_requirements: !ruby/object:Gem::Requirement
|
|
78
84
|
requirements:
|
|
79
|
-
- - "
|
|
85
|
+
- - "~>"
|
|
80
86
|
- !ruby/object:Gem::Version
|
|
81
|
-
version: '0'
|
|
87
|
+
version: '5.0'
|
|
82
88
|
- !ruby/object:Gem::Dependency
|
|
83
89
|
name: down
|
|
84
90
|
requirement: !ruby/object:Gem::Requirement
|
|
85
91
|
requirements:
|
|
86
|
-
- - "
|
|
92
|
+
- - "~>"
|
|
87
93
|
- !ruby/object:Gem::Version
|
|
88
|
-
version: '0'
|
|
94
|
+
version: '5.0'
|
|
89
95
|
type: :runtime
|
|
90
96
|
prerelease: false
|
|
91
97
|
version_requirements: !ruby/object:Gem::Requirement
|
|
92
98
|
requirements:
|
|
93
|
-
- - "
|
|
99
|
+
- - "~>"
|
|
94
100
|
- !ruby/object:Gem::Version
|
|
95
|
-
version: '0'
|
|
101
|
+
version: '5.0'
|
|
96
102
|
- !ruby/object:Gem::Dependency
|
|
97
103
|
name: openssl-oaep
|
|
98
104
|
requirement: !ruby/object:Gem::Requirement
|
|
99
105
|
requirements:
|
|
100
|
-
- - "
|
|
106
|
+
- - "~>"
|
|
101
107
|
- !ruby/object:Gem::Version
|
|
102
|
-
version: '0'
|
|
108
|
+
version: '0.1'
|
|
103
109
|
type: :runtime
|
|
104
110
|
prerelease: false
|
|
105
111
|
version_requirements: !ruby/object:Gem::Requirement
|
|
106
112
|
requirements:
|
|
107
|
-
- - "
|
|
113
|
+
- - "~>"
|
|
108
114
|
- !ruby/object:Gem::Version
|
|
109
|
-
version: '0'
|
|
110
|
-
description: Ruby client for DIDWW API v3
|
|
115
|
+
version: '0.1'
|
|
116
|
+
description: Ruby client for the DIDWW JSON:API v3, covering DID inventory, voice
|
|
117
|
+
in/out trunks, regulatory documents, exports, and emergency calling services.
|
|
111
118
|
email:
|
|
112
119
|
- alex.k@didww.com
|
|
113
120
|
executables: []
|
|
@@ -158,6 +165,7 @@ files:
|
|
|
158
165
|
- examples/regions.rb
|
|
159
166
|
- examples/shared_capacity_groups.rb
|
|
160
167
|
- examples/voice_in_trunk_groups.rb
|
|
168
|
+
- examples/voice_in_trunk_sip_registration.rb
|
|
161
169
|
- examples/voice_in_trunks.rb
|
|
162
170
|
- examples/voice_out_trunks.rb
|
|
163
171
|
- lib/didww.rb
|