didww-v3 6.0.0 → 6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1aa466258f09ae99e535c21caeec5d0e274fe77288dd99adb4161e426e7eb45c
4
- data.tar.gz: 8c35e66178155231aa3d3fbe8fc9907f22f6aabaacfbad406b926adb093bbb92
3
+ metadata.gz: 9cdeedbb317141c84de28bbedb4f652b858793e257d8d84f70155b08fc54623c
4
+ data.tar.gz: c48319beb5292ef53595e7905d4ac62ff0e59685bcb57eb48c2f7e6efbb7bd3e
5
5
  SHA512:
6
- metadata.gz: 8cb5d98d5d0bc28a2c082fe730f0bb314e7e812d75d5326f502149d4c6b645fd876069113f9271ee3f9ac77ac052aa49573b8e1c2d8f79ff9fefb445a8df764e
7
- data.tar.gz: ab528d899547eac431d54968f117a8fb1426a8d520fd7ce6660d9730f0f7981ba32df7ef5e2ee00c87d619be7af2d0c0b1746c20b2691266bf7c61b7f8ed885f
6
+ metadata.gz: 42be04280b6673a827de5f4bc680f032f28d72437137d081455f2168aa02e92d3861b7fc842e6c70406e6495d1f377745f90a23ead254fdf0a5a6caf40417d63
7
+ data.tar.gz: e6aeccf261500d0307c5cbc7eae427880a853d008ce28639ff2512daa1ea1cb40c54962b3d0f972f7e3ea0a73274bc40a6a8a57b9df45689b9c0ddfb83987acb
data/CHANGELOG.md CHANGED
@@ -4,6 +4,19 @@ 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.0] - 2026-05-04
8
+ ### Added
9
+ - 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):
10
+ - `enabled_sip_registration` (bool) — enables SIP registration; the API generates `incoming_auth_*` credentials when set true and requires `port` to be blank.
11
+ - `use_did_in_ruri` (bool) — uses DID number in R-URI; requires SIP registration to be enabled.
12
+ - `network_protocol_priority` (string enum) — `force_ipv4` / `force_ipv6` / `any` / `prefer_ipv4` / `prefer_ipv6`. Constants exposed as `NETWORK_PROTOCOL_PRIORITY_*` and `NETWORK_PROTOCOL_PRIORITIES`.
13
+ - `diversion_inject_mode` (string enum) — `none` / `did_number`. Constants exposed as `DIVERSION_INJECT_MODE_*` and `DIVERSION_INJECT_MODES`.
14
+ - `cnam_lookup` (bool) — enables CNAM resolution for inbound calls.
15
+ - `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.
16
+ - `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`.
17
+ - `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`.
18
+ - `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.
19
+
7
20
  ## [6.0.0] - 2026-04-24
8
21
  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
22
 
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:
@@ -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,58 @@ 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
+ # Clear host/port only if they were already set — never emit a
354
+ # spurious `host: null` on a fresh config, which would otherwise
355
+ # widen every PATCH/POST body. If the trunk had a host, we have
356
+ # to send `host: null` explicitly so the server clears it.
357
+ self[:host] = nil if attributes.key?('host') && !self[:host].nil?
358
+ self[:port] = nil if attributes.key?('port') && !self[:port].nil?
359
+ when false
360
+ # Server requires use_did_in_ruri = false whenever sip_registration
361
+ # is disabled. Always emit it on the wire so the server's check
362
+ # passes regardless of the prior state of the field.
363
+ self[:use_did_in_ruri] = false
364
+ else
365
+ # `nil` (or any non-bool) — caller is unsetting the field. No
366
+ # cascade: dependent fields stay as the caller left them.
367
+ end
368
+ self[:enabled_sip_registration] = val
369
+ end
370
+
371
+ def host=(val)
372
+ unless val.nil?
373
+ # Setting host implies sip_registration is disabled (server-side
374
+ # validation), which in turn implies use_did_in_ruri = false.
375
+ # Always emit both on the wire so the server's checks pass
376
+ # without the caller having to know the rule set.
377
+ self[:enabled_sip_registration] = false
378
+ self[:use_did_in_ruri] = false
379
+ end
380
+ self[:host] = val
381
+ end
260
382
  end
261
383
  end
262
384
  end
data/lib/didww/version.rb CHANGED
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module DIDWW
3
- VERSION = '6.0.0'.freeze
3
+ VERSION = '6.1.0'.freeze
4
4
  end
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.0.0
4
+ version: 6.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Korobeinikov