The 10-minute timeout added for review feedback would have aborted
LND's long-poll subscription streams (/v2/invoices/subscribe and
/v2/router/track), which legitimately stay open until an invoice
settles or a payment resolves - breaking real-time notifications for
any invoice paid more than 10 minutes after creation.
The wrapper now honors a per-call options.timeout (0 disables the
bound, axios semantics; the 10-minute default still applies everywhere
else), and both subscription calls pass timeout: 0. They also copy the
options object instead of mutating it: addInvoice hands the
session-cached options to subscribeToInvoice, so setting the timeout
in place would have leaked an unbounded timeout to every subsequent
request for that node (getOptions resets form/body/qs but not
timeout).
Verified on the regtest fixture: with a websocket client connected as
alice's frontend, creating an invoice opens the subscription stream,
it survives idle, and paying it from the CLN node delivers the SETTLED
event over the websocket in real time. The per-call override was also
verified directly (timeout: 1000 aborts a slow upstream with
ECONNABORTED; timeout: 0 waits it out). Both API suites (31 read + 12
write checks) re-pass.
Review feedback on #1638 flagged two issues in the wrapper:
1. Array form values (eclair's ignoreNodeIds on findroutebetweennodes)
encoded as "ignoreNodeIds=a,b" via String(), and worse, an empty
array produced "ignoreNodeIds=" which Eclair's pubkey list parser
rejects - breaking the default findroute path that worked under
request-promise (qs omitted empty arrays). Arrays are now omitted
when empty and comma-joined when not, matching Eclair's CsvSeq list
format. Verified against the fixture: route eclair->bob->carol is
found with an empty ignore list and disappears when bob is ignored.
Under request-promise's qs indexed encoding (ignoreNodeIds[0]=...)
Eclair never matched the field name, so the ignore list was silently
dropped; this change makes it effective for the first time.
2. The shared transport had no request timeout, so a hung upstream
held connections open indefinitely. Added a 10-minute bound,
sized to the slowest legitimate operations (LND's /v2/router/send
streams up to timeout_seconds=600; slow CLN channel operations get
req.setTimeout(600000) upstream).
Both API suites (31 read + 12 write checks) re-pass on the fixture.
request has been deprecated since 2020 with an unfixed SSRF advisory and
pins vulnerable copies of form-data (critical), qs, tough-cookie and
uuid - 8 of the 13 remaining production audit findings, none fixable by
version bumps (issue #1634, item 1).
All 36 backend files that imported request-promise now use a small
compatibility wrapper (server/utils/request.ts) backed by axios, which
is already a production dependency. The wrapper accepts the existing
options shape (qs, form - object or pre-encoded string, body,
baseUrl/uri, rejectUnauthorized, json), resolves with the response body
directly, and rejects with a plain object mirroring request-promise's
StatusCodeError/RequestError shape, so CommonService.handleError works
unchanged (ECONNREFUSED -> 503, Eclair StatusCodeError -> 500, nested
error body extraction). Auth headers are excluded from rejected errors
so they cannot leak into logs. Callers without json: true (block
explorer, currency rates) still get raw text bodies, and LND's
line-delimited /v2/router/send stream still surfaces as a string for
the existing parser.
Only behavioral code change: CLN verifyMessage used request-promise's
callback style and was ported to the same promise style as signMessage;
four Eclair handlers gained explicit returns to satisfy
noImplicitReturns once the import became typed.
Production npm audit drops from 13 findings (2 critical) to 6 low, all
in the crypto-browserify/elliptic chain tracked in #1634.
Verified against the docker regtest fixture with 43 API checks across
LND, Core Lightning and Eclair: reads, invoice creation, a routed LND
payment over the streaming endpoint, cross-implementation payments from
CLN and Eclair, message sign/verify, channel backup to disk, and
bad-invoice/node-unreachable error mapping. Lint and both production
builds are clean.