diff --git a/backend/controllers/lnd/webSocketClient.js b/backend/controllers/lnd/webSocketClient.js index 3271546a..943a1b56 100644 --- a/backend/controllers/lnd/webSocketClient.js +++ b/backend/controllers/lnd/webSocketClient.js @@ -44,7 +44,9 @@ export class LNDWebSocketClient { this.subscribeToInvoice = (options, selectedNode, rHash) => { rHash = rHash?.replace(/\+/g, '-')?.replace(/[/]/g, '_'); this.logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'WebSocketClient', msg: 'Subscribing to Invoice ' + rHash + ' ..' }); - options.url = selectedNode.settings.lnServerUrl + '/v2/invoices/subscribe/' + rHash; + // Copy the options: the caller may pass the session-cached object, and the + // long poll needs an unbounded timeout without leaking it to other calls. + options = { ...options, url: selectedNode.settings.lnServerUrl + '/v2/invoices/subscribe/' + rHash, timeout: 0 }; request(options).then((msg) => { this.logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'WebSocketClient', msg: 'Invoice Information Received for ' + rHash }); if (typeof msg === 'string') { @@ -67,7 +69,9 @@ export class LNDWebSocketClient { }; this.subscribeToPayment = (options, selectedNode, paymentHash) => { this.logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'WebSocketClient', msg: 'Subscribing to Payment ' + paymentHash + ' ..' }); - options.url = selectedNode.settings.lnServerUrl + '/v2/router/track/' + paymentHash; + // Copy the options: the long poll needs an unbounded timeout without + // leaking it to other calls sharing the object. + options = { ...options, url: selectedNode.settings.lnServerUrl + '/v2/router/track/' + paymentHash, timeout: 0 }; request(options).then((msg) => { this.logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'WebSocketClient', msg: 'Payment Information Received for ' + paymentHash }); msg['type'] = 'payment'; diff --git a/backend/utils/request.js b/backend/utils/request.js index 9506a90e..72a776f4 100644 --- a/backend/utils/request.js +++ b/backend/utils/request.js @@ -16,7 +16,9 @@ const buildConfig = (options, method) => { // 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 + // Callers can override per request; 0 disables the bound entirely, which + // the LND invoice/payment subscription streams need (open until settled). + timeout: options.timeout !== null && options.timeout !== undefined ? options.timeout : 600000 }; if (options.baseUrl) { config.baseURL = options.baseUrl; diff --git a/server/controllers/lnd/webSocketClient.ts b/server/controllers/lnd/webSocketClient.ts index c4434d3e..ef3a791b 100644 --- a/server/controllers/lnd/webSocketClient.ts +++ b/server/controllers/lnd/webSocketClient.ts @@ -58,7 +58,9 @@ export class LNDWebSocketClient { public subscribeToInvoice = (options: any, selectedNode: SelectedNode, rHash: string) => { rHash = rHash?.replace(/\+/g, '-')?.replace(/[/]/g, '_'); this.logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'WebSocketClient', msg: 'Subscribing to Invoice ' + rHash + ' ..' }); - options.url = selectedNode.settings.lnServerUrl + '/v2/invoices/subscribe/' + rHash; + // Copy the options: the caller may pass the session-cached object, and the + // long poll needs an unbounded timeout without leaking it to other calls. + options = { ...options, url: selectedNode.settings.lnServerUrl + '/v2/invoices/subscribe/' + rHash, timeout: 0 }; request(options).then((msg) => { this.logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'WebSocketClient', msg: 'Invoice Information Received for ' + rHash }); if (typeof msg === 'string') { @@ -82,7 +84,9 @@ export class LNDWebSocketClient { public subscribeToPayment = (options: any, selectedNode: SelectedNode, paymentHash: string) => { this.logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'WebSocketClient', msg: 'Subscribing to Payment ' + paymentHash + ' ..' }); - options.url = selectedNode.settings.lnServerUrl + '/v2/router/track/' + paymentHash; + // Copy the options: the long poll needs an unbounded timeout without + // leaking it to other calls sharing the object. + options = { ...options, url: selectedNode.settings.lnServerUrl + '/v2/router/track/' + paymentHash, timeout: 0 }; request(options).then((msg) => { this.logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'WebSocketClient', msg: 'Payment Information Received for ' + paymentHash }); msg['type'] = 'payment'; diff --git a/server/utils/request.ts b/server/utils/request.ts index 9114aa90..38ea1cdd 100644 --- a/server/utils/request.ts +++ b/server/utils/request.ts @@ -19,7 +19,9 @@ const buildConfig = (options, method: string) => { // 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 + // Callers can override per request; 0 disables the bound entirely, which + // the LND invoice/payment subscription streams need (open until settled). + timeout: options.timeout !== null && options.timeout !== undefined ? options.timeout : 600000 }; if (options.baseUrl) { config.baseURL = options.baseUrl; } if (options.qs && Object.keys(options.qs).length > 0) { config.params = options.qs; }