From c3f4bc92ceaea63a0badb84cb97c4f66ac90d89d Mon Sep 17 00:00:00 2001 From: Jacob Page Date: Tue, 3 Jun 2025 19:39:29 -0700 Subject: [PATCH] fix: handle DOMException in Boom constructor DOMException instances have getters that require correct 'this' context. Hoek.clone() creates copies that lose the internal structure needed for these getters to work, causing "Value of 'this' must be of DOMException" errors. Extract message and name properties before cloning to avoid this issue. --- lib/index.js | 13 ++++++++++++ test/index.js | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/lib/index.js b/lib/index.js index 709b754..df080d9 100755 --- a/lib/index.js +++ b/lib/index.js @@ -71,6 +71,19 @@ exports.Boom = class extends Error { constructor(messageOrError, options = {}) { if (messageOrError instanceof Error) { + // Handle DOMException which has getters that require correct 'this' context + if (messageOrError.constructor.name === 'DOMException') { + // Extract message and name before cloning since Hoek.clone() creates a copy + // that loses the internal structure needed for DOMException getters to work + const message = messageOrError.message; + const name = messageOrError.name; + + // Create a new regular Error instead of cloning the DOMException + const newError = new Error(message); + newError.name = name; + return exports.boomify(newError, options); + } + return exports.boomify(Hoek.clone(messageOrError), options); } diff --git a/test/index.js b/test/index.js index 8c7c5a7..b808233 100755 --- a/test/index.js +++ b/test/index.js @@ -123,6 +123,31 @@ describe('Boom', () => { expect(err.output.payload.error).to.equal('Unknown'); }); + it('handles DOMException from fetch abort', async () => { + + // Create a DOMException from fetch abort + const controller = new AbortController(); + controller.abort(); + + let fetchDOMException; + try { + await fetch('https://example.com', { signal: controller.signal }); + } + catch (err) { + fetchDOMException = err; + } + + expect(fetchDOMException).to.be.instanceof(DOMException); + expect(fetchDOMException.name).to.equal('AbortError'); + + const err = new Boom.Boom(fetchDOMException, { statusCode: 400 }); + + expect(err.isBoom).to.be.true(); + expect(err.output.statusCode).to.equal(400); + expect(err.message).to.equal('This operation was aborted'); + expect(err.name).to.equal('AbortError'); + }); + describe('instanceof', () => { it('identifies a boom object', () => { @@ -926,6 +951,36 @@ describe('Boom', () => { } }); }); + + it('handles DOMException without throwing', () => { + + const domException = new DOMException('Test message', 'TestError'); + const err = Boom.badImplementation(domException); + + expect(err.isDeveloperError).to.equal(true); + expect(err.isServer).to.be.true(); + expect(err.output.statusCode).to.equal(500); + expect(err.message).to.equal('Test message'); + expect(err.name).to.equal('TestError'); + expect(err.output.payload.error).to.equal('Internal Server Error'); + expect(err.output.payload.message).to.equal('An internal server error occurred'); + }); + + it('handles DOMException with custom message and name', () => { + + const domException = new DOMException('Custom error message', 'CustomError'); + const err = Boom.badImplementation(domException); + + expect(err.isDeveloperError).to.equal(true); + expect(err.isServer).to.be.true(); + expect(err.output.statusCode).to.equal(500); + expect(err.message).to.equal('Custom error message'); + expect(err.name).to.equal('CustomError'); + expect(err.output.payload.error).to.equal('Internal Server Error'); + expect(err.output.payload.message).to.equal('An internal server error occurred'); + }); + + }); describe('stack trace', () => {