diff --git a/CHANGELOG.md b/CHANGELOG.md index e7abb0d86a9..a932b5690f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -693,3 +693,7 @@ Released with 1.0.0-beta.37 code base. ## [Unreleased] + +### Fixed + +- Fixed broken fetch for Node.js > 18.x and fixed double callback (#6381) diff --git a/package.json b/package.json index bd00894ce4b..dd8cafc2638 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,7 @@ "chai": "^4.2.0", "chai-as-promised": "^7.1.1", "core-js": "^3.6.5", + "cross-fetch": "^4.0.0", "crypto-browserify": "^3.12.0", "crypto-js": "^3.3.0", "decache": "^4.6.0", diff --git a/packages/web3-providers-http/package.json b/packages/web3-providers-http/package.json index b90c4f2ce3b..ed99ba94c6f 100644 --- a/packages/web3-providers-http/package.json +++ b/packages/web3-providers-http/package.json @@ -14,8 +14,8 @@ "types": "types/index.d.ts", "main": "lib/index.js", "dependencies": { - "abortcontroller-polyfill": "^1.7.3", - "cross-fetch": "^3.1.4", + "abortcontroller-polyfill": "^1.7.5", + "cross-fetch": "^4.0.0", "es6-promise": "^4.2.8", "web3-core-helpers": "1.10.1" }, diff --git a/packages/web3-providers-http/src/index.js b/packages/web3-providers-http/src/index.js index dfe9363bcd9..7a67af2aed7 100644 --- a/packages/web3-providers-http/src/index.js +++ b/packages/web3-providers-http/src/index.js @@ -24,16 +24,16 @@ */ var errors = require('web3-core-helpers').errors; +var fetch = require('cross-fetch'); var http = require('http'); var https = require('https'); // Apply missing polyfill for IE -require('cross-fetch/polyfill'); require('es6-promise').polyfill(); // import abortController if abortController is not included in node if(typeof global !== "undefined" && !global.AbortController){ - require('abortcontroller-polyfill/dist/polyfill-patch-fetch') + require('abortcontroller-polyfill/dist/polyfill-patch-fetch'); } /** @@ -46,6 +46,7 @@ var HttpProvider = function HttpProvider(host, options) { this.timeout = options.timeout || 0; this.headers = options.headers; this.agent = options.agent; + this.forceGlobalFetch = options.forceGlobalFetch || false; this.connected = false; // keepAlive is true unless explicitly set to false @@ -74,6 +75,7 @@ HttpProvider.prototype.send = function (payload, callback) { }; var headers = {}; var controller; + var fetchFunc = this.forceGlobalFetch ? globalThis.fetch : fetch; if (typeof AbortController !== 'undefined') { controller = new AbortController(); @@ -138,11 +140,25 @@ HttpProvider.prototype.send = function (payload, callback) { } // Response is a stream data so should be awaited for json response - response.json().then(function (data) { - callback(null, data); - }).catch(function (error) { - callback(errors.InvalidResponse(response)); - }); + response + .json() + .then( + function (data) { + callback(null, data); + }, + function () { + response + .text() + .then( + function (text) { + callback(errors.InvalidResponse(text)); + }, + function () { + callback(errors.InvalidResponse("")); + } + ); + } + ); }; var failed = function (error) { @@ -152,14 +168,14 @@ HttpProvider.prototype.send = function (payload, callback) { if (error.name === 'AbortError') { callback(errors.ConnectionTimeout(this.timeout)); + return; } callback(errors.InvalidConnection(this.host, error)); - } + }; - fetch(this.host, options) - .then(success.bind(this)) - .catch(failed.bind(this)); + fetchFunc(this.host, options) + .then(success.bind(this), failed.bind(this)); }; HttpProvider.prototype.disconnect = function () { diff --git a/packages/web3-providers-http/types/index.d.ts b/packages/web3-providers-http/types/index.d.ts index d3a4818fa23..c915613c3e0 100644 --- a/packages/web3-providers-http/types/index.d.ts +++ b/packages/web3-providers-http/types/index.d.ts @@ -49,6 +49,7 @@ export class HttpProvider extends HttpProviderBase { timeout: number; headers?: HttpHeader[]; agent?: HttpProviderAgent; + forceGlobalFetch?: boolean; connected: boolean; constructor(host?: string, options?: HttpProviderOptions); diff --git a/test/httpprovider.js b/test/httpprovider.js index 6814b92bba3..f0a7e5b2b23 100644 --- a/test/httpprovider.js +++ b/test/httpprovider.js @@ -7,7 +7,9 @@ var http = require('http'); var https = require('https'); var Web3 = require('../packages/web3'); var HttpProvider = require('../packages/web3-providers-http'); +// Mock test with globalThis.fetch func var fetchMock = require('fetch-mock'); +require('cross-fetch/polyfill'); function isObject(object) { return object != null && typeof object === 'object'; @@ -72,15 +74,14 @@ describe('web3-providers-http', function () { describe('send', function () { it('should fail with invalid remote node connection', async function () { - var provider = new HttpProvider('http://localhost:8545'); + var provider = new HttpProvider('http://localhost:8545', { forceGlobalFetch: true }); var web3 = new Web3(provider); await expect(web3.eth.getChainId()).to.be.rejectedWith(Error, "CONNECTION ERROR: Couldn't connect to node http://localhost:8545."); - }); it('should fail for non-json format response', async function () { - var provider = new HttpProvider('/fetchMock'); + var provider = new HttpProvider('/fetchMock', { forceGlobalFetch: true }); var web3 = new Web3(provider); fetchMock.mock('/fetchMock', 'Testing non-json format response'); @@ -90,7 +91,7 @@ describe('web3-providers-http', function () { }); it('should timeout by delayed response', async function () { - var provider = new HttpProvider('/fetchMock', { timeout: 500 }); + var provider = new HttpProvider('/fetchMock', { forceGlobalFetch: true, timeout: 500 }); var web3 = new Web3(provider); fetchMock.mock('/fetchMock', 'Testing non-json format response', { delay: 1000 }); @@ -100,7 +101,7 @@ describe('web3-providers-http', function () { }); it('should send basic async request', async function () { - var provider = new HttpProvider('/fetchMock'); + var provider = new HttpProvider('/fetchMock', { forceGlobalFetch: true }); var reqObject = { 'jsonrpc': '2.0',