From 1efaa24d72d3df19c1ac5db485a8068d94a960e5 Mon Sep 17 00:00:00 2001 From: saubyk <39208279+saubyk@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:48:56 -0700 Subject: [PATCH] Address review: CSV-encode array form fields and bound request timeout 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. --- backend/utils/request.js | 18 ++++++++++++++++-- server/utils/request.ts | 15 +++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/backend/utils/request.js b/backend/utils/request.js index 7e36b907..9506a90e 100644 --- a/backend/utils/request.js +++ b/backend/utils/request.js @@ -12,7 +12,11 @@ const buildConfig = (options, method) => { const config = { url: options.url && options.url !== '' ? options.url : options.uri, method: method || options.method || 'GET', - headers: options.headers ? { ...options.headers } : {} + headers: options.headers ? { ...options.headers } : {}, + // Bound hung upstreams; 10 minutes accommodates the slowest legitimate + // operations (LND's /v2/router/send streams up to timeout_seconds=600 and + // slow CLN channel operations get req.setTimeout(600000) upstream). + timeout: 600000 }; if (options.baseUrl) { config.baseURL = options.baseUrl; @@ -31,7 +35,17 @@ const buildConfig = (options, method) => { else { const params = new URLSearchParams(); Object.entries(options.form).forEach(([key, value]) => { - if (value !== null && value !== undefined) { + if (value === null || value === undefined) { + return; + } + if (Array.isArray(value)) { + // Eclair parses list fields as comma-separated values; omit empty lists + // (request-promise's qs encoding also dropped them). + if (value.length > 0) { + params.append(key, value.join(',')); + } + } + else { params.append(key, String(value)); } }); diff --git a/server/utils/request.ts b/server/utils/request.ts index fc0ea892..9114aa90 100644 --- a/server/utils/request.ts +++ b/server/utils/request.ts @@ -15,7 +15,11 @@ const buildConfig = (options, method: string) => { const config: any = { url: options.url && options.url !== '' ? options.url : options.uri, method: method || options.method || 'GET', - headers: options.headers ? { ...options.headers } : {} + headers: options.headers ? { ...options.headers } : {}, + // Bound hung upstreams; 10 minutes accommodates the slowest legitimate + // operations (LND's /v2/router/send streams up to timeout_seconds=600 and + // slow CLN channel operations get req.setTimeout(600000) upstream). + timeout: 600000 }; if (options.baseUrl) { config.baseURL = options.baseUrl; } if (options.qs && Object.keys(options.qs).length > 0) { config.params = options.qs; } @@ -27,7 +31,14 @@ const buildConfig = (options, method: string) => { } else { const params = new URLSearchParams(); Object.entries(options.form).forEach(([key, value]) => { - if (value !== null && value !== undefined) { params.append(key, String(value)); } + if (value === null || value === undefined) { return; } + if (Array.isArray(value)) { + // Eclair parses list fields as comma-separated values; omit empty lists + // (request-promise's qs encoding also dropped them). + if (value.length > 0) { params.append(key, value.join(',')); } + } else { + params.append(key, String(value)); + } }); config.data = params; }