diff --git a/.changeset/root-handler-crash-payments-disabled.md b/.changeset/root-handler-crash-payments-disabled.md new file mode 100644 index 00000000..49f5313d --- /dev/null +++ b/.changeset/root-handler-crash-payments-disabled.md @@ -0,0 +1,5 @@ +--- +"nostream": patch +--- + +fix: prevent crash in NIP-11 relay information document when payments settings are absent diff --git a/src/handlers/request-handlers/root-request-handler.ts b/src/handlers/request-handlers/root-request-handler.ts index f322057f..e792c0df 100644 --- a/src/handlers/request-handlers/root-request-handler.ts +++ b/src/handlers/request-handlers/root-request-handler.ts @@ -105,20 +105,22 @@ export const rootRequestHandler = (request: Request, response: Response, next: N search_supported: settings.nip50?.enabled ?? false, }, payments_url: paymentsUrl.toString(), - fees: Object.getOwnPropertyNames(settings.payments.feeSchedules).reduce( - (prev, feeName) => { - const feeSchedules = settings.payments.feeSchedules[feeName] as FeeSchedule[] - - return { - ...prev, - [feeName]: feeSchedules.reduce( - (fees, fee) => (fee.enabled ? [...fees, { amount: fee.amount, unit: 'msats' }] : fees), - [], - ), - } - }, - {} as Record, - ), + fees: settings.payments + ? Object.getOwnPropertyNames(settings.payments.feeSchedules).reduce( + (prev, feeName) => { + const feeSchedules = settings.payments.feeSchedules[feeName] as FeeSchedule[] + + return { + ...prev, + [feeName]: feeSchedules.reduce( + (fees, fee) => (fee.enabled ? [...fees, { amount: fee.amount, unit: 'msats' }] : fees), + [], + ), + } + }, + {} as Record, + ) + : {}, } response diff --git a/test/unit/handlers/request-handlers/root-request-handler.spec.ts b/test/unit/handlers/request-handlers/root-request-handler.spec.ts index a72bec27..597984de 100644 --- a/test/unit/handlers/request-handlers/root-request-handler.spec.ts +++ b/test/unit/handlers/request-handlers/root-request-handler.spec.ts @@ -224,6 +224,16 @@ describe('rootRequestHandler', () => { const restrictedDoc = res.send.firstCall.args[0] expect(restrictedDoc.limitation.restricted_writes).to.equal(true) }) + + it('returns empty fees instead of crashing when the payments block is absent', () => { + const { payments, ...settingsWithoutPayments } = baseSettings + createSettingsStub.returns(settingsWithoutPayments) + + expect(() => rootRequestHandler(req, res, next)).to.not.throw() + + const doc = res.send.firstCall.args[0] + expect(doc.fees).to.deep.equal({}) + }) }) describe('when serving HTML', () => {