diff --git a/src/invoicing.js b/src/invoicing.js index 617c930..32e9e1e 100644 --- a/src/invoicing.js +++ b/src/invoicing.js @@ -255,18 +255,26 @@ class PurpleInvoiceManager { // Checks the status of an invoice once. Returns true if paid, false otherwise. async check_invoice_is_paid(label) { + const params = { label } + const timeout_ms = parseInt(process.env.LN_INVOICE_CHECK_TIMEOUT_MS) || 60000 + let timeout_id = null + + const timeout_promise = new Promise((resolve) => { + timeout_id = setTimeout(() => { + resolve(undefined) + }, timeout_ms) + }) + + const waitinvoice_promise = this.ln_rpc({ method: "waitinvoice", params }) + .then((res) => res.error ? false : true) + .catch(() => undefined) + try { - const params = { label } - return new Promise(async (resolve, reject) => { - setTimeout(() => { - resolve(undefined) - }, parseInt(process.env.LN_INVOICE_CHECK_TIMEOUT_MS) || 60000) - const res = await this.ln_rpc({ method: "waitinvoice", params }) - resolve(res.error ? false : true) - }) - } - catch { - return undefined + return await Promise.race([waitinvoice_promise, timeout_promise]) + } finally { + if (timeout_id) { + clearTimeout(timeout_id) + } } } diff --git a/test/ln_flow.test.js b/test/ln_flow.test.js index 4247b7a..a4d9fd2 100644 --- a/test/ln_flow.test.js +++ b/test/ln_flow.test.js @@ -240,3 +240,29 @@ test('LN Flow — Background polling processes paid invoices without client chec t.end(); }); + +test('LN Flow — check_invoice_is_paid handles LN RPC rejection without unhandled rejection', async (t) => { + const purple_api_controller = await PurpleTestController.new(t); + const manager = purple_api_controller.purple_api.invoice_manager; + manager.ln_rpc = async () => { + // Simulate the production failure mode where a promise rejection reason can be undefined. + throw undefined + } + + let unhandledRejectionReceived = false + const handleUnhandledRejection = () => { + unhandledRejectionReceived = true + } + process.on('unhandledRejection', handleUnhandledRejection) + + try { + const result = await manager.check_invoice_is_paid('some-label') + await new Promise((resolve) => setImmediate(resolve)) + t.same(result, undefined) + t.equal(unhandledRejectionReceived, false) + } finally { + process.removeListener('unhandledRejection', handleUnhandledRejection) + } + + t.end() +})