nwc-ruby 0.2.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 97f270975d59347524e31943926149e2d361833a5ab9dd7c580cf00281b5fec9
4
- data.tar.gz: 99b7a6b3a40c8c0f7ca30408f34afb653fab4c5afb8c69327e79be3a00afa663
3
+ metadata.gz: d5d73701179764ad26d1ad95d212e1abd5856788a951591e3f8489b16e66d0f0
4
+ data.tar.gz: 97047cb484ab707715cd0ce4590d356a9b0e23a307729c808f0098f043c94dba
5
5
  SHA512:
6
- metadata.gz: 96bc19c5c7ee1ac4794c76be6aa9e64b9aeddf7d89d5a4df88134104f07e65a40c1466efa54ffea9df546d55a92bb06c27e2ee1e8397d3c9459272bca6065526
7
- data.tar.gz: 20f46cf66670993db5c8fa351e2bc95b611c7ad994dd7d64495927ffac5347c8b9a1a8767cb19dd16a2deb2dea869086ae8ae606a13211f55f3546156526afd8
6
+ metadata.gz: f7bfd6655a81a49a9c04e534a872be4be1438af498b77f452a14706feb8535b9421745cc4f15c488339532019e1f94fb0b56d14c55a565b440f6232fea067b74
7
+ data.tar.gz: 8af52b0b23683c1c62e0d64f4c02597c82c9aa64ae277da7d7099208453ba596db58b3762abdb728a08c2d063013befc39f8c699c39ce4e66aa921c77e90a839
data/CHANGELOG.md CHANGED
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.1] — 2026-04-20
11
+
12
+ ### Fixed
13
+
14
+ - Fixed all RuboCop offenses (CI now passes clean).
15
+ - Extracted `start_heartbeat` / `start_poll` from `run_one_connection` to reduce
16
+ method length and perceived complexity.
17
+ - Corrected README and CHANGELOG: transport uses 15 s ping keepalive (not 30 s
18
+ ping + 45 s pong deadline — the pong deadline was removed in 0.1.1 due to
19
+ async-websocket 0.30 API changes).
20
+ - Fixed string quoting, hash alignment, guard clause, and line length offenses
21
+ across Rakefile, `bin/nwc_test`, `client.rb`, `test_runner.rb`, and
22
+ `relay_connection.rb`.
23
+ - Excluded `lib/nwc-ruby.rb` from `Naming/FileName` (compatibility shim).
24
+
10
25
  ## [0.2.0] — 2026-04-20
11
26
 
12
27
  ### Changed
@@ -52,9 +67,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
52
67
  `multi_pay_keysend`, `make_invoice`, `lookup_invoice`, `list_transactions`,
53
68
  `get_balance`, `get_info`, `sign_message`.
54
69
  - Notification listener (kinds 23196 and 23197) with dedupe by `payment_hash`.
55
- - Reliable long-running `Transport::RelayConnection`: RFC 6455 ping (30 s),
56
- pong deadline (45 s), forced recycle (5 min), capped exponential backoff,
57
- SIGTERM/SIGINT handling.
70
+ - Reliable long-running `Transport::RelayConnection`: RFC 6455 ping (15 s),
71
+ forced recycle (5 min), capped exponential backoff, SIGTERM/SIGINT handling.
58
72
  - `NwcRuby.test` and `NwcRuby.test_notifications` diagnostic methods, backed
59
73
  by `NwcRuby::TestRunner`. Announces read-only vs read+write, exercises every
60
74
  advertised method, pays a Lightning address if the code is read+write.
data/README.md CHANGED
@@ -34,8 +34,8 @@ heartbeats, zombie-TCP detection, reconnects, and backoff. You call methods.
34
34
  - **Both encryption schemes** — NIP-44 v2 when the wallet advertises it
35
35
  (validated against [paulmillr's test vectors][vectors]), NIP-04 fallback for
36
36
  wallets that haven't migrated.
37
- - **Bulletproof long-running transport** — 30 s ping, 45 s pong deadline, 5-min
38
- forced recycle, capped exponential backoff, clean SIGTERM handling. Built on
37
+ - **Bulletproof long-running transport** — 15 s ping keepalive, 5-min forced
38
+ recycle, capped exponential backoff, clean SIGTERM handling. Built on
39
39
  [async-websocket][aw] (no dead EventMachine dependency).
40
40
  - **Two diagnostic methods** — `NwcRuby.test` (info, read tests, write test
41
41
  if applicable) and `NwcRuby.test_notifications` (listen forever in a separate
@@ -211,10 +211,10 @@ end
211
211
  ```
212
212
 
213
213
  Under the hood, this subscribes to both kind 23196 (NIP-04) and kind 23197
214
- (NIP-44 v2), dedupes by `payment_hash`, sends a WebSocket ping every 30 seconds,
215
- reconnects with exponential backoff if the pong deadline is missed, and
216
- force-recycles the connection every 5 minutes as a belt-and-suspenders check
217
- against middleboxes that silently drop stale TCP streams.
214
+ (NIP-44 v2), dedupes by `payment_hash`, sends a WebSocket ping every 15 seconds
215
+ to keep middleboxes from idle-closing the socket, reconnects with capped
216
+ exponential backoff on failure, and force-recycles the connection every 5 minutes
217
+ as a belt-and-suspenders check against silently dead TCP streams.
218
218
 
219
219
  #### Resuming after a restart
220
220
 
@@ -143,7 +143,7 @@ module NwcRuby
143
143
  # @param kinds [Array<Integer>] notification kinds to listen for. Defaults
144
144
  # to both NIP-04 (23196) and NIP-44 v2 (23197). The listener dedupes by
145
145
  # `payment_hash`, so receiving both is safe.
146
- def subscribe_to_notifications(since: Time.now.to_i,
146
+ def subscribe_to_notifications(since: Time.now.to_i, # rubocop:disable Metrics/MethodLength
147
147
  kinds: [NIP47::Methods::KIND_NOTIFICATION_NIP04,
148
148
  NIP47::Methods::KIND_NOTIFICATION_NIP44],
149
149
  sub_id: "nwc-#{SecureRandom.hex(4)}",
@@ -168,7 +168,10 @@ module NwcRuby
168
168
  @notification_connection = conn
169
169
 
170
170
  conn.on_open do |c|
171
- @logger.info("[nwc] subscribing sub_id=#{sub_id} client_pubkey=#{@connection_string.client_pubkey} wallet_pubkey=#{@connection_string.wallet_pubkey} kinds=#{kinds.inspect} since=#{since}")
171
+ @logger.info(
172
+ "[nwc] subscribing sub_id=#{sub_id} client_pubkey=#{@connection_string.client_pubkey} " \
173
+ "wallet_pubkey=#{@connection_string.wallet_pubkey} kinds=#{kinds.inspect} since=#{since}"
174
+ )
172
175
  c.send_req(sub_id: sub_id, filters: filters)
173
176
  end
174
177
 
@@ -56,8 +56,8 @@ module NwcRuby
56
56
 
57
57
  if @pay_to_lightning_address
58
58
  if @info.read_only?
59
- warn!("pay_to_lightning_address was provided but this is a READ-ONLY code — it does not support pay_invoice.")
60
- warn!(" → Generate a read+write NWC code if you need to test outbound payments.")
59
+ warn!('pay_to_lightning_address was provided but this is a READ-ONLY code — it does not support pay_invoice.')
60
+ warn!(' → Generate a read+write NWC code if you need to test outbound payments.')
61
61
  @out.puts
62
62
  else
63
63
  run_write_tests
@@ -205,7 +205,10 @@ module NwcRuby
205
205
  try('lookup_invoice (payment_hash from previous step)', NIP47::Methods::LOOKUP_INVOICE) do
206
206
  result = @client.lookup_invoice(payment_hash: @test_payment_hash)
207
207
  @out.puts " #{DIM}state=#{result['state']} amount=#{result['amount']} msats#{CLR}"
208
- warn!("lookup_invoice: `state` is #{result['state'].inspect}, expected 'pending' for a fresh invoice (optional per NIP-47)") unless result['state'] == 'pending'
208
+ unless result['state'] == 'pending'
209
+ warn!("lookup_invoice: `state` is #{result['state'].inspect}, " \
210
+ "expected 'pending' for a fresh invoice (optional per NIP-47)")
211
+ end
209
212
  end
210
213
  end
211
214
 
@@ -252,8 +255,12 @@ module NwcRuby
252
255
  fail!('make_invoice: `payment_hash` is not a 64-char hex')
253
256
  end
254
257
  fail!('make_invoice: `amount` should echo 1000') unless result['amount'] == 1_000
255
- warn!("make_invoice: `type` is #{result['type'].inspect}, expected 'incoming' (optional per NIP-47)") unless result['type'] == 'incoming'
256
- warn!("make_invoice: `state` is #{result['state'].inspect}, expected 'pending' (optional per NIP-47)") unless result['state'] == 'pending'
258
+ unless result['type'] == 'incoming'
259
+ warn!("make_invoice: `type` is #{result['type'].inspect}, expected 'incoming' (optional per NIP-47)")
260
+ end
261
+ return if result['state'] == 'pending'
262
+
263
+ warn!("make_invoice: `state` is #{result['state'].inspect}, expected 'pending' (optional per NIP-47)")
257
264
  end
258
265
 
259
266
  # --- Lightning address resolver ----------------------------------------
@@ -135,39 +135,8 @@ module NwcRuby
135
135
 
136
136
  Async::WebSocket::Client.connect(endpoint) do |conn|
137
137
  @conn = conn
138
-
139
- heartbeat = top.async do
140
- loop do
141
- conn.send_ping
142
- conn.flush
143
- @logger.debug("[nwc] ping sent")
144
-
145
- sleep @ping_interval
146
- break if @stop
147
-
148
- if Async::Clock.now - opened_at > @recycle_interval
149
- raise TransportError, "recycle (#{@recycle_interval}s)"
150
- end
151
- end
152
- end
153
-
154
- # Some relays (e.g. strfry) store ephemeral events temporarily but
155
- # do not push them to active subscriptions. Periodic re-subscribe
156
- # forces the relay to return any newly-stored events.
157
- poll = if @poll_interval && @poll_cb
158
- top.async do
159
- loop do
160
- sleep @poll_interval
161
- break if @stop
162
-
163
- begin
164
- @poll_cb.call(self)
165
- rescue StandardError => e
166
- @logger.warn("[nwc] poll error: #{e.message}")
167
- end
168
- end
169
- end
170
- end
138
+ heartbeat = start_heartbeat(top, conn, opened_at)
139
+ poll = start_poll(top)
171
140
 
172
141
  @open_cb&.call(self)
173
142
  read_loop(conn)
@@ -183,6 +152,38 @@ module NwcRuby
183
152
  end
184
153
  end
185
154
 
155
+ def start_heartbeat(top, conn, opened_at)
156
+ top.async do
157
+ loop do
158
+ conn.send_ping
159
+ conn.flush
160
+ @logger.debug('[nwc] ping sent')
161
+
162
+ sleep @ping_interval
163
+ break if @stop
164
+
165
+ raise TransportError, "recycle (#{@recycle_interval}s)" if Async::Clock.now - opened_at > @recycle_interval
166
+ end
167
+ end
168
+ end
169
+
170
+ def start_poll(top)
171
+ return unless @poll_interval && @poll_cb
172
+
173
+ top.async do
174
+ loop do
175
+ sleep @poll_interval
176
+ break if @stop
177
+
178
+ begin
179
+ @poll_cb.call(self)
180
+ rescue StandardError => e
181
+ @logger.warn("[nwc] poll error: #{e.message}")
182
+ end
183
+ end
184
+ end
185
+ end
186
+
186
187
  def read_loop(conn)
187
188
  while (message = conn.read)
188
189
  break if @stop
@@ -224,7 +225,8 @@ module NwcRuby
224
225
  # Close the socket to unblock the read loop.
225
226
  begin
226
227
  @conn&.close
227
- rescue StandardError # rubocop:disable Lint/SuppressedException
228
+ rescue StandardError
229
+ nil
228
230
  end
229
231
  end
230
232
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NwcRuby
4
- VERSION = '0.2.0'
4
+ VERSION = '0.2.1'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nwc-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - MegalithicBTC
@@ -222,7 +222,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
222
222
  - !ruby/object:Gem::Version
223
223
  version: '0'
224
224
  requirements: []
225
- rubygems_version: 3.6.9
225
+ rubygems_version: 3.6.8
226
226
  specification_version: 4
227
227
  summary: Ruby client for Nostr Wallet Connect (NIP-47) with safe long-running relay
228
228
  connections.