diff --git a/.changeset/hot-foxes-perform.md b/.changeset/hot-foxes-perform.md new file mode 100644 index 0000000..f8534ee --- /dev/null +++ b/.changeset/hot-foxes-perform.md @@ -0,0 +1,5 @@ +--- +"@remix-run/web-fetch": patch +--- + +fix: Allow subclass methods of Headers to be called correctly diff --git a/packages/fetch/src/headers.js b/packages/fetch/src/headers.js index 86ae2aa..4095c4a 100644 --- a/packages/fetch/src/headers.js +++ b/packages/fetch/src/headers.js @@ -104,55 +104,6 @@ export default class Headers extends URLSearchParams { []; super(result); - - // Returning a Proxy that will lowercase key names, validate parameters and sort keys - // eslint-disable-next-line no-constructor-return - return new Proxy(this, { - get(target, p, receiver) { - switch (p) { - case 'append': - case 'set': - /** - * @param {string} name - * @param {string} value - */ - return (name, value) => { - validateHeaderName(name); - validateHeaderValue(name, String(value)); - return URLSearchParams.prototype[p].call( - target, - String(name).toLowerCase(), - String(value) - ); - }; - - case 'delete': - case 'has': - case 'getAll': - /** - * @param {string} name - */ - return name => { - validateHeaderName(name); - // @ts-ignore - return URLSearchParams.prototype[p].call( - target, - String(name).toLowerCase() - ); - }; - - case 'keys': - return () => { - target.sort(); - return new Set(URLSearchParams.prototype.keys.call(target)).keys(); - }; - - default: - return Reflect.get(target, p, receiver); - } - } - /* c8 ignore next */ - }); } get [Symbol.toStringTag]() { @@ -181,6 +132,56 @@ export default class Headers extends URLSearchParams { return value; } + /** + * @param {string} name + */ + getAll(name) { + validateHeaderName(name); + return super.getAll(String(name).toLowerCase()); + } + + /** + * @param {string} name + * @param {string} value + */ + append(name, value) { + validateHeaderName(name); + validateHeaderValue(name, String(value)); + return super.append( + String(name).toLowerCase(), + String(value) + ); + } + + /** + * @param {string} name + */ + delete(name) { + validateHeaderName(name); + return super.delete(String(name).toLowerCase()); + } + + /** + * @param {string} name + */ + has(name) { + validateHeaderName(name); + return super.has(String(name).toLowerCase()); + } + + /** + * @param {string} name + * @param {string} value + */ + set(name, value) { + validateHeaderName(name); + validateHeaderValue(name, String(value)); + return super.set( + String(name).toLowerCase(), + String(value) + ); + } + /** * @param {(value: string, key: string, parent: this) => void} callback * @param {any} thisArg @@ -199,6 +200,11 @@ export default class Headers extends URLSearchParams { } } + keys() { + this.sort(); + return new Set(super.keys()).keys(); + } + /** * @returns {IterableIterator} */ @@ -272,7 +278,18 @@ export default class Headers extends URLSearchParams { */ Object.defineProperties( Headers.prototype, - ['get', 'entries', 'forEach', 'values'].reduce((result, property) => { + [ + 'append', + 'delete', + 'entries', + 'forEach', + 'get', + 'getAll', + 'has', + 'keys', + 'set', + 'values' + ].reduce((result, property) => { result[property] = {enumerable: true}; return result; }, /** @type {Record} */ ({})) diff --git a/packages/fetch/test/headers.js b/packages/fetch/test/headers.js index 8f9e168..11e44f0 100644 --- a/packages/fetch/test/headers.js +++ b/packages/fetch/test/headers.js @@ -347,4 +347,34 @@ describe('Headers', () => { // eslint-disable-next-line quotes expect(util.format(headers)).to.equal("{ a: [ '1', '3' ], b: '2', host: 'thehost' }"); }); + + it('should have the correct prototype chain', () => { + const headers = new Headers(); + + expect(headers).to.be.instanceOf(Headers); + expect(Object.getPrototypeOf(headers)).to.equal(Headers.prototype); + }); + + it('should have the correct prototype chain when extended', () => { + class MyHeaders extends Headers {} + + const headers = new MyHeaders(); + + expect(headers).to.be.instanceOf(MyHeaders); + expect(headers).to.be.instanceOf(Headers); + expect(Object.getPrototypeOf(headers)).to.equal(MyHeaders.prototype); + }); + + it('should call the method of the subclass', () => { + class MyHeaders extends Headers { + append(_name, _value) { + return 'subclass method called'; + } + } + + const headers = new MyHeaders(); + const result = headers.append('Content-Type', 'application/json'); + + expect(result).to.equal('subclass method called'); + }); });